summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/bluetooth/BluetoothPrefs.java40
-rw-r--r--src/com/android/bluetooth/a2dpsink/A2dpSinkService.java3
-rw-r--r--src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java63
-rw-r--r--src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java37
-rw-r--r--src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java138
-rw-r--r--src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java104
-rw-r--r--src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java106
-rw-r--r--src/com/android/bluetooth/avrcpcontroller/BrowseTree.java9
-rw-r--r--src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java201
-rw-r--r--src/com/android/bluetooth/btservice/AdapterService.java1
-rw-r--r--src/com/android/bluetooth/hfp/HeadsetStateMachine.java26
-rw-r--r--src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java2
-rw-r--r--src/com/android/bluetooth/mapclient/MceStateMachine.java28
-rw-r--r--src/com/android/bluetooth/mapclient/MnsObexServer.java4
-rw-r--r--src/com/android/bluetooth/mapclient/MnsService.java6
-rw-r--r--src/com/android/bluetooth/mapclient/obex/BmessageParser.java6
-rw-r--r--src/com/android/bluetooth/mapclient/obex/ObexTime.java43
-rw-r--r--src/com/android/bluetooth/pan/PanService.java13
-rw-r--r--src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java2
-rw-r--r--src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java66
-rw-r--r--src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java124
-rw-r--r--src/com/android/bluetooth/pbapclient/PbapClientService.java36
22 files changed, 710 insertions, 348 deletions
diff --git a/src/com/android/bluetooth/BluetoothPrefs.java b/src/com/android/bluetooth/BluetoothPrefs.java
new file mode 100644
index 000000000..2c7c87aaa
--- /dev/null
+++ b/src/com/android/bluetooth/BluetoothPrefs.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that routes to Bluetooth settings when launched
+ */
+public class BluetoothPrefs extends Activity {
+
+ public static final String BLUETOOTH_SETTING_ACTION = "android.settings.BLUETOOTH_SETTINGS";
+ public static final String BLUETOOTH_SETTING_CATEGORY = "android.intent.category.DEFAULT";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent launchIntent = new Intent();
+ launchIntent.setAction(BLUETOOTH_SETTING_ACTION);
+ launchIntent.addCategory(BLUETOOTH_SETTING_CATEGORY);
+ startActivity(launchIntent);
+ finish();
+ }
+}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index e41def075..5271cc791 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -413,7 +413,8 @@ public class A2dpSinkService extends ProfileService {
if (state == StackEvent.AUDIO_STATE_STARTED) {
mA2dpSinkStreamHandler.obtainMessage(
A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
- } else if (state == StackEvent.AUDIO_STATE_STOPPED) {
+ } else if (state == StackEvent.AUDIO_STATE_STOPPED
+ || state == StackEvent.AUDIO_STATE_REMOTE_SUSPEND) {
mA2dpSinkStreamHandler.obtainMessage(
A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index 5e3b3567c..5aa3cbbd5 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -17,6 +17,7 @@
package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClientCall;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
@@ -24,10 +25,9 @@ import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaPlayer;
-import android.media.session.PlaybackState;
-
import android.os.Handler;
import android.os.Message;
+import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import com.android.bluetooth.R;
@@ -183,8 +183,9 @@ public class A2dpSinkStreamHandler extends Handler {
break;
case AUDIO_FOCUS_CHANGE:
+ mAudioFocus = (int) message.obj;
// message.obj is the newly granted audio focus.
- switch ((int) message.obj) {
+ switch (mAudioFocus) {
case AudioManager.AUDIOFOCUS_GAIN:
removeMessages(DELAYED_PAUSE);
// Begin playing audio, if we paused the remote, send a play now.
@@ -228,7 +229,7 @@ public class A2dpSinkStreamHandler extends Handler {
case DELAYED_PAUSE:
if (BluetoothMediaBrowserService.getPlaybackState()
- == PlaybackState.STATE_PLAYING && !inCallFromStreamingDevice()) {
+ == PlaybackStateCompat.STATE_PLAYING && !inCallFromStreamingDevice()) {
sendAvrcpPause();
mSentPause = true;
mStreamAvailable = false;
@@ -245,12 +246,9 @@ public class A2dpSinkStreamHandler extends Handler {
*/
private void requestAudioFocusIfNone() {
if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+ if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) {
requestAudioFocus();
}
- // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this.
- // This function immediately exits if we have a MediaPlayer object.
- requestMediaKeyFocus();
}
private synchronized int requestAudioFocus() {
@@ -277,8 +275,11 @@ public class A2dpSinkStreamHandler extends Handler {
}
/**
- * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
- * aware of the fact that Bluetooth is playing audio.
+ * Plays a silent audio sample so that MediaSessionService will be aware of the fact that
+ * Bluetooth is playing audio.
+ *
+ * Creates a new MediaPlayer if one does not already exist. Repeat calls to this function are
+ * safe and will result in the silent audio sample again.
*
* This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
* chosen to use it.
@@ -286,25 +287,25 @@ public class A2dpSinkStreamHandler extends Handler {
private synchronized void requestMediaKeyFocus() {
if (DBG) Log.d(TAG, "requestMediaKeyFocus()");
- if (mMediaPlayer != null) return;
-
- AudioAttributes attrs = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .build();
-
- mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
- mAudioManager.generateAudioSessionId());
if (mMediaPlayer == null) {
- Log.e(TAG, "Failed to initialize media player. You may not get media key events");
- return;
- }
+ AudioAttributes attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+
+ mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
+ mAudioManager.generateAudioSessionId());
+ if (mMediaPlayer == null) {
+ Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+ return;
+ }
- mMediaPlayer.setLooping(false);
- mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
- Log.e(TAG, "Silent media player error: " + what + ", " + extra);
- releaseMediaKeyFocus();
- return false;
- });
+ mMediaPlayer.setLooping(false);
+ mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+ Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+ releaseMediaKeyFocus();
+ return false;
+ });
+ }
mMediaPlayer.start();
BluetoothMediaBrowserService.setActive(true);
@@ -313,7 +314,6 @@ public class A2dpSinkStreamHandler extends Handler {
private synchronized void abandonAudioFocus() {
if (DBG) Log.d(TAG, "abandonAudioFocus()");
stopFluorideStreaming();
- releaseMediaKeyFocus();
mAudioManager.abandonAudioFocus(mAudioFocusListener);
mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
}
@@ -336,9 +336,11 @@ public class A2dpSinkStreamHandler extends Handler {
private void startFluorideStreaming() {
mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
mA2dpSinkService.informAudioTrackGainNative(1.0f);
+ requestMediaKeyFocus();
}
private void stopFluorideStreaming() {
+ releaseMediaKeyFocus();
mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
}
@@ -362,7 +364,10 @@ public class A2dpSinkStreamHandler extends Handler {
}
HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
if (targetDevice != null && headsetClientService != null) {
- return headsetClientService.getCurrentCalls(targetDevice).size() > 0;
+ List<BluetoothHeadsetClientCall> currentCalls =
+ headsetClientService.getCurrentCalls(targetDevice);
+ if (currentCalls == null) return false;
+ return currentCalls.size() > 0;
}
return false;
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 64e63df00..56684cd7c 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -172,9 +172,11 @@ public class AvrcpControllerService extends ProfileService {
}
}
+ // If we don't find a node in the tree then do not have any way to browse for the contents.
+ // Return an empty list instead.
if (requestedNode == null) {
if (DBG) Log.d(TAG, "Didn't find a node");
- return null;
+ return new ArrayList(0);
} else {
if (!requestedNode.isCached()) {
if (DBG) Log.d(TAG, "node is not cached");
@@ -412,9 +414,10 @@ public class AvrcpControllerService extends ProfileService {
if (stateMachine != null) {
PlayerApplicationSettings supportedSettings =
PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS,
+ supportedSettings);
}
- /* Do nothing */
-
}
private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
@@ -426,10 +429,12 @@ public class AvrcpControllerService extends ProfileService {
AvrcpControllerStateMachine stateMachine = getStateMachine(device);
if (stateMachine != null) {
- PlayerApplicationSettings desiredSettings =
+ PlayerApplicationSettings currentSettings =
PlayerApplicationSettings.makeSettings(playerAttribRsp);
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS,
+ currentSettings);
}
- /* Do nothing */
}
// Browsing related JNI callbacks.
@@ -711,7 +716,7 @@ public class AvrcpControllerService extends ProfileService {
/**
* Send button press commands to addressed device
*
- * @param keyCode key code as defined in AVRCP specification
+ * @param keyCode key code as defined in AVRCP specification
* @param keyState 0 = key pressed, 1 = key released
* @return command was sent
*/
@@ -720,7 +725,7 @@ public class AvrcpControllerService extends ProfileService {
/**
* Send group navigation commands
*
- * @param keyCode next/previous
+ * @param keyCode next/previous
* @param keyState state
* @return command was sent
*/
@@ -741,7 +746,7 @@ public class AvrcpControllerService extends ProfileService {
* Send response to set absolute volume
*
* @param absVol new volume
- * @param label label
+ * @param label label
*/
public native void sendAbsVolRspNative(byte[] address, int absVol, int label);
@@ -749,8 +754,8 @@ public class AvrcpControllerService extends ProfileService {
* Register for any volume level changes
*
* @param rspType type of response
- * @param absVol current volume
- * @param label label
+ * @param absVol current volume
+ * @param label label
*/
public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
int label);
@@ -764,7 +769,7 @@ public class AvrcpControllerService extends ProfileService {
* Fetch the current now playing list
*
* @param start first index to retrieve
- * @param end last index to retrieve
+ * @param end last index to retrieve
*/
public native void getNowPlayingListNative(byte[] address, int start, int end);
@@ -772,7 +777,7 @@ public class AvrcpControllerService extends ProfileService {
* Fetch the current folder's listing
*
* @param start first index to retrieve
- * @param end last index to retrieve
+ * @param end last index to retrieve
*/
public native void getFolderListNative(byte[] address, int start, int end);
@@ -780,7 +785,7 @@ public class AvrcpControllerService extends ProfileService {
* Fetch the listing of players
*
* @param start first index to retrieve
- * @param end last index to retrieve
+ * @param end last index to retrieve
*/
public native void getPlayerListNative(byte[] address, int start, int end);
@@ -788,15 +793,15 @@ public class AvrcpControllerService extends ProfileService {
* Change the current browsed folder
*
* @param direction up/down
- * @param uid folder unique id
+ * @param uid folder unique id
*/
public native void changeFolderPathNative(byte[] address, byte direction, long uid);
/**
* Play item with provided uid
*
- * @param scope scope of item to played
- * @param uid song unique id
+ * @param scope scope of item to played
+ * @param uid song unique id
* @param uidCounter counter
*/
public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter);
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 66571c4e7..c319364c1 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -24,10 +24,10 @@ import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Message;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import android.util.SparseArray;
@@ -42,6 +42,7 @@ import com.android.internal.util.StateMachine;
import java.util.ArrayList;
import java.util.List;
+
/**
* Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
* and interactions with a remote controlable device.
@@ -76,11 +77,15 @@ class AvrcpControllerStateMachine extends StateMachine {
static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
+ static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217;
+ static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218;
//300->399 Events for Browsing
static final int MESSAGE_GET_FOLDER_ITEMS = 300;
static final int MESSAGE_PLAY_ITEM = 301;
static final int MSG_AVRCP_PASSTHRU = 302;
+ static final int MSG_AVRCP_SET_SHUFFLE = 303;
+ static final int MSG_AVRCP_SET_REPEAT = 304;
static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
@@ -218,26 +223,19 @@ class AvrcpControllerStateMachine extends StateMachine {
mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
BluetoothMediaBrowserService.notifyChanged(mService
.sBrowseTree.mRootNode);
- BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
mBrowsingConnected = true;
}
synchronized void onBrowsingDisconnected() {
if (!mBrowsingConnected) return;
- mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR);
+ mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR);
mAddressedPlayer.updateCurrentTrack(null);
mBrowseTree.mNowPlayingNode.setCached(false);
BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
- PlaybackState.Builder pbb = new PlaybackState.Builder();
- pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
- 1.0f).setActions(0);
- pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected));
- BluetoothMediaBrowserService.notifyChanged(pbb.build());
mService.sBrowseTree.mRootNode.removeChild(
mBrowseTree.mRootNode);
BluetoothMediaBrowserService.notifyChanged(mService
.sBrowseTree.mRootNode);
- BluetoothMediaBrowserService.trackChanged(null);
mBrowsingConnected = false;
}
@@ -298,8 +296,9 @@ class AvrcpControllerStateMachine extends StateMachine {
@Override
public void enter() {
if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
- broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
} else {
logD("ReEnteringConnected");
}
@@ -315,14 +314,14 @@ class AvrcpControllerStateMachine extends StateMachine {
removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
ABS_VOL_TIMEOUT_MILLIS);
- setAbsVolume(msg.arg1, msg.arg2);
+ handleAbsVolumeRequest(msg.arg1, msg.arg2);
return true;
case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
mVolumeNotificationLabel = msg.arg1;
mService.sendRegisterAbsVolRspNative(mDeviceAddress,
NOTIFICATION_RSP_TYPE_INTERIM,
- getAbsVolumeResponse(), mVolumeNotificationLabel);
+ getAbsVolume(), mVolumeNotificationLabel);
return true;
case MESSAGE_GET_FOLDER_ITEMS:
@@ -338,6 +337,14 @@ class AvrcpControllerStateMachine extends StateMachine {
passThru(msg.arg1);
return true;
+ case MSG_AVRCP_SET_REPEAT:
+ setRepeat(msg.arg1);
+ return true;
+
+ case MSG_AVRCP_SET_SHUFFLE:
+ setShuffle(msg.arg1);
+ return true;
+
case MESSAGE_PROCESS_TRACK_CHANGED:
mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
@@ -347,11 +354,14 @@ class AvrcpControllerStateMachine extends StateMachine {
mAddressedPlayer.setPlayStatus(msg.arg1);
BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
if (mAddressedPlayer.getPlaybackState().getState()
- == PlaybackState.STATE_PLAYING
- && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE
- && !shouldRequestFocus()) {
+ == PlaybackStateCompat.STATE_PLAYING
+ && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE) {
+ if (shouldRequestFocus()) {
+ mSessionCallbacks.onPrepare();
+ } else {
sendMessage(MSG_AVRCP_PASSTHRU,
AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+ }
}
return true;
@@ -378,6 +388,18 @@ class AvrcpControllerStateMachine extends StateMachine {
}
return true;
+ case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
+ mAddressedPlayer.setSupportedPlayerApplicationSettings(
+ (PlayerApplicationSettings) msg.obj);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ return true;
+
+ case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS:
+ mAddressedPlayer.setCurrentPlayerApplicationSettings(
+ (PlayerApplicationSettings) msg.obj);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ return true;
+
case DISCONNECT:
transitionTo(mDisconnecting);
return true;
@@ -435,6 +457,20 @@ class AvrcpControllerStateMachine extends StateMachine {
return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
|| (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
}
+
+ private void setRepeat(int repeatMode) {
+ mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
+ new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{
+ PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
+ PlayerApplicationSettings.REPEAT_STATUS, repeatMode)});
+ }
+
+ private void setShuffle(int shuffleMode) {
+ mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
+ new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{
+ PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
+ PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)});
+ }
}
// Handle the get folder listing action
@@ -554,7 +590,7 @@ class AvrcpControllerStateMachine extends StateMachine {
case MESSAGE_GET_FOLDER_ITEMS:
if (!mBrowseNode.equals(msg.obj)) {
if (shouldAbort(mBrowseNode.getScope(),
- ((BrowseTree.BrowseNode) msg.obj).getScope())) {
+ ((BrowseTree.BrowseNode) msg.obj).getScope())) {
mAbort = true;
}
deferMessage(msg);
@@ -576,8 +612,8 @@ class AvrcpControllerStateMachine extends StateMachine {
* necessary.
*
* @return true: a new folder in the same scope
- * a new player while fetching contents of a folder
- * false: other cases, specifically Now Playing while fetching a folder
+ * a new player while fetching contents of a folder
+ * false: other cases, specifically Now Playing while fetching a folder
*/
private boolean shouldAbort(int currentScope, int fetchScope) {
if ((currentScope == fetchScope)
@@ -674,31 +710,60 @@ class AvrcpControllerStateMachine extends StateMachine {
@Override
public void enter() {
onBrowsingDisconnected();
+ BluetoothMediaBrowserService.trackChanged(null);
+ BluetoothMediaBrowserService.addressedPlayerChanged(null);
broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
transitionTo(mDisconnected);
}
}
+ /**
+ * Handle a request to align our local volume with the volume of a remote device. If
+ * we're assuming the source volume is fixed then a response of ABS_VOL_MAX will always be
+ * sent and no volume adjustment action will be taken on the sink side.
+ *
+ * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
+ * @param label Volume notification label
+ */
+ private void handleAbsVolumeRequest(int absVol, int label) {
+ logD("handleAbsVolumeRequest: absVol = " + absVol + ", label = " + label);
+ if (mIsVolumeFixed) {
+ logD("Source volume is assumed to be fixed, responding with max volume");
+ absVol = ABS_VOL_BASE;
+ } else {
+ mVolumeChangedNotificationsToIgnore++;
+ removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+ sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
+ ABS_VOL_TIMEOUT_MILLIS);
+ setAbsVolume(absVol);
+ }
+ mService.sendAbsVolRspNative(mDeviceAddress, absVol, label);
+ }
+
+ /**
+ * Align our volume with a requested absolute volume level
+ *
+ * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
+ */
+ private void setAbsVolume(int absVol) {
+ int maxLocalVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int curLocalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ int reqLocalVolume = (maxLocalVolume * absVol) / ABS_VOL_BASE;
+ logD("setAbsVolme: absVol = " + absVol + ", reqLocal = " + reqLocalVolume
+ + ", curLocal = " + curLocalVolume + ", maxLocal = " + maxLocalVolume);
- private void setAbsVolume(int absVol, int label) {
- int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
- logD(" setAbsVolume =" + absVol + " maxVol = " + maxVolume
- + " cur = " + currIndex + " new = " + newIndex);
/*
* In some cases change in percentage is not sufficient enough to warrant
* change in index values which are in range of 0-15. For such cases
* no action is required
*/
- if (newIndex != currIndex) {
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
+ if (reqLocalVolume != curLocalVolume) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, reqLocalVolume,
AudioManager.FLAG_SHOW_UI);
}
- mService.sendAbsVolRspNative(mDeviceAddress, getAbsVolumeResponse(), label);
}
- private int getAbsVolumeResponse() {
+ private int getAbsVolume() {
if (mIsVolumeFixed) {
return ABS_VOL_BASE;
}
@@ -708,7 +773,7 @@ class AvrcpControllerStateMachine extends StateMachine {
return newIndex;
}
- MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
+ MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
logD("onPlay");
@@ -781,6 +846,19 @@ class AvrcpControllerStateMachine extends StateMachine {
BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
sendMessage(MESSAGE_PLAY_ITEM, node);
}
+
+ @Override
+ public void onSetRepeatMode(int repeatMode) {
+ logD("onSetRepeatMode");
+ sendMessage(MSG_AVRCP_SET_REPEAT, repeatMode);
+ }
+
+ @Override
+ public void onSetShuffleMode(int shuffleMode) {
+ logD("onSetShuffleMode");
+ sendMessage(MSG_AVRCP_SET_SHUFFLE, shuffleMode);
+
+ }
};
protected void broadcastConnectionStateChanged(int currentState) {
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index bed38d905..4736acffa 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -17,8 +17,9 @@
package com.android.bluetooth.avrcpcontroller;
import android.media.MediaMetadata;
-import android.media.session.PlaybackState;
import android.os.SystemClock;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import java.util.Arrays;
@@ -41,27 +42,31 @@ class AvrcpPlayer {
public static final int FEATURE_PREVIOUS = 48;
public static final int FEATURE_BROWSING = 59;
- private int mPlayStatus = PlaybackState.STATE_NONE;
- private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+ private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
+ private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
private long mPlayTimeUpdate = 0;
private float mPlaySpeed = 1;
private int mId;
private String mName = "";
private int mPlayerType;
- private byte[] mPlayerFeatures;
- private long mAvailableActions;
+ private byte[] mPlayerFeatures = new byte[16];
+ private long mAvailableActions = PlaybackStateCompat.ACTION_PREPARE;
private MediaMetadata mCurrentTrack;
- private PlaybackState mPlaybackState;
+ private PlaybackStateCompat mPlaybackStateCompat;
+ private PlayerApplicationSettings mSupportedPlayerApplicationSettings =
+ new PlayerApplicationSettings();
+ private PlayerApplicationSettings mCurrentPlayerApplicationSettings;
AvrcpPlayer() {
mId = INVALID_ID;
//Set Default Actions in case Player data isn't available.
- mAvailableActions = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
- | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
- | PlaybackState.ACTION_STOP;
- PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+ mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY
+ | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+ | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+ | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PREPARE;
+ PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
.setActions(mAvailableActions);
- mPlaybackState = playbackStateBuilder.build();
+ mPlaybackStateCompat = playbackStateBuilder.build();
}
AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) {
@@ -70,10 +75,10 @@ class AvrcpPlayer {
mPlayStatus = playStatus;
mPlayerType = playerType;
mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
- updateAvailableActions();
- PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+ PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
.setActions(mAvailableActions);
- mPlaybackState = playbackStateBuilder.build();
+ mPlaybackStateCompat = playbackStateBuilder.build();
+ updateAvailableActions();
}
public int getId() {
@@ -87,7 +92,8 @@ class AvrcpPlayer {
public void setPlayTime(int playTime) {
mPlayTime = playTime;
mPlayTimeUpdate = SystemClock.elapsedRealtime();
- mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+ mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState(
+ mPlayStatus, mPlayTime,
mPlaySpeed).build();
}
@@ -97,30 +103,48 @@ class AvrcpPlayer {
public void setPlayStatus(int playStatus) {
mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
- - mPlaybackState.getLastPositionUpdateTime());
+ - mPlaybackStateCompat.getLastPositionUpdateTime());
mPlayStatus = playStatus;
switch (mPlayStatus) {
- case PlaybackState.STATE_STOPPED:
+ case PlaybackStateCompat.STATE_STOPPED:
mPlaySpeed = 0;
break;
- case PlaybackState.STATE_PLAYING:
+ case PlaybackStateCompat.STATE_PLAYING:
mPlaySpeed = 1;
break;
- case PlaybackState.STATE_PAUSED:
+ case PlaybackStateCompat.STATE_PAUSED:
mPlaySpeed = 0;
break;
- case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackStateCompat.STATE_FAST_FORWARDING:
mPlaySpeed = 3;
break;
- case PlaybackState.STATE_REWINDING:
+ case PlaybackStateCompat.STATE_REWINDING:
mPlaySpeed = -3;
break;
}
- mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+ mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState(
+ mPlayStatus, mPlayTime,
mPlaySpeed).build();
}
+ public void setSupportedPlayerApplicationSettings(
+ PlayerApplicationSettings playerApplicationSettings) {
+ mSupportedPlayerApplicationSettings = playerApplicationSettings;
+ updateAvailableActions();
+ }
+
+ public void setCurrentPlayerApplicationSettings(
+ PlayerApplicationSettings playerApplicationSettings) {
+ Log.d(TAG, "Settings changed");
+ mCurrentPlayerApplicationSettings = playerApplicationSettings;
+ MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
+ session.setRepeatMode(mCurrentPlayerApplicationSettings.getSetting(
+ PlayerApplicationSettings.REPEAT_STATUS));
+ session.setShuffleMode(mCurrentPlayerApplicationSettings.getSetting(
+ PlayerApplicationSettings.SHUFFLE_STATUS));
+ }
+
public int getPlayStatus() {
return mPlayStatus;
}
@@ -131,17 +155,22 @@ class AvrcpPlayer {
return (mPlayerFeatures[byteNumber] & bitMask) == bitMask;
}
- public PlaybackState getPlaybackState() {
+ public boolean supportsSetting(int settingType, int settingValue) {
+ return mSupportedPlayerApplicationSettings.supportsSetting(settingType, settingValue);
+ }
+
+ public PlaybackStateCompat getPlaybackState() {
if (DBG) {
Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime);
}
- return mPlaybackState;
+ return mPlaybackStateCompat;
}
public synchronized void updateCurrentTrack(MediaMetadata update) {
if (update != null) {
long trackNumber = update.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
- mPlaybackState = new PlaybackState.Builder(mPlaybackState).setActiveQueueItemId(
+ mPlaybackStateCompat = new PlaybackStateCompat.Builder(
+ mPlaybackStateCompat).setActiveQueueItemId(
trackNumber - 1).build();
}
mCurrentTrack = update;
@@ -153,26 +182,37 @@ class AvrcpPlayer {
private void updateAvailableActions() {
if (supportsFeature(FEATURE_PLAY)) {
- mAvailableActions = mAvailableActions | PlaybackState.ACTION_PLAY;
+ mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PLAY;
}
if (supportsFeature(FEATURE_STOP)) {
- mAvailableActions = mAvailableActions | PlaybackState.ACTION_STOP;
+ mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_STOP;
}
if (supportsFeature(FEATURE_PAUSE)) {
- mAvailableActions = mAvailableActions | PlaybackState.ACTION_PAUSE;
+ mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PAUSE;
}
if (supportsFeature(FEATURE_REWIND)) {
- mAvailableActions = mAvailableActions | PlaybackState.ACTION_REWIND;
+ mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_REWIND;
}
if (supportsFeature(FEATURE_FAST_FORWARD)) {
- mAvailableActions = mAvailableActions | PlaybackState.ACTION_FAST_FORWARD;
+ mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_FAST_FORWARD;
}
if (supportsFeature(FEATURE_FORWARD)) {
- mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_NEXT;
+ mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
}
if (supportsFeature(FEATURE_PREVIOUS)) {
- mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
}
+ if (mSupportedPlayerApplicationSettings.supportsSetting(
+ PlayerApplicationSettings.REPEAT_STATUS)) {
+ mAvailableActions |= PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
+ }
+ if (mSupportedPlayerApplicationSettings.supportsSetting(
+ PlayerApplicationSettings.SHUFFLE_STATUS)) {
+ mAvailableActions |= PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE;
+ }
+ mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat)
+ .setActions(mAvailableActions).build();
+
if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions);
}
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 304d5a2c9..a0b1224ee 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -16,15 +16,22 @@
package com.android.bluetooth.avrcpcontroller;
+import android.app.PendingIntent;
+import android.content.Intent;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
import android.os.Bundle;
-import android.service.media.MediaBrowserService;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
+import androidx.media.MediaBrowserServiceCompat;
+
+import com.android.bluetooth.BluetoothPrefs;
import com.android.bluetooth.R;
import java.util.ArrayList;
@@ -37,45 +44,48 @@ import java.util.List;
* The applications are expected to use MediaBrowser (see API) and all the music
* browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
*
- * The current behavior of MediaSession exposed by this service is as follows:
- * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is
- * connected and first starts playing. Before it starts playing we do not active the session.
+ * The current behavior of MediaSessionCompat exposed by this service is as follows:
+ * 1. MediaSessionCompat is active (i.e. SystemUI and other overview UIs can see updates) when
+ * device is connected and first starts playing. Before it starts playing we do not activate the
+ * session.
* 1.1 The session is active throughout the duration of connection.
* 2. The session is de-activated when the device disconnects. It will be connected again when (1)
* happens.
*/
-public class BluetoothMediaBrowserService extends MediaBrowserService {
+public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
private static final String TAG = "BluetoothMediaBrowserService";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
- private MediaSession mSession;
+ private MediaSessionCompat mSession;
// Browsing related structures.
- private List<MediaSession.QueueItem> mMediaQueue = new ArrayList<>();
+ private List<MediaSessionCompat.QueueItem> mMediaQueue = new ArrayList<>();
+
+ // Error messaging extras
+ public static final String ERROR_RESOLUTION_ACTION_INTENT =
+ "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
+ public static final String ERROR_RESOLUTION_ACTION_LABEL =
+ "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
/**
- * Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and
- * MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
+ * Initialize this BluetoothMediaBrowserService, creating our MediaSessionCompat, MediaPlayer
+ * and MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
*/
@Override
public void onCreate() {
if (DBG) Log.d(TAG, "onCreate");
super.onCreate();
- // Create and configure the MediaSession
- mSession = new MediaSession(this, TAG);
+ // Create and configure the MediaSessionCompat
+ mSession = new MediaSessionCompat(this, TAG);
setSessionToken(mSession.getSessionToken());
- mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
- | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
+ | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
mSession.setQueue(mMediaQueue);
- PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder();
- playbackStateBuilder.setState(PlaybackState.STATE_ERROR,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0);
- playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected));
- mSession.setPlaybackState(playbackStateBuilder.build());
+ setErrorPlaybackState();
sBluetoothMediaBrowserService = this;
}
@@ -89,11 +99,30 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
}
}
+ private void setErrorPlaybackState() {
+ Bundle extras = new Bundle();
+ extras.putString(ERROR_RESOLUTION_ACTION_LABEL,
+ getString(R.string.bluetooth_connect_action));
+ Intent launchIntent = new Intent();
+ launchIntent.setAction(BluetoothPrefs.BLUETOOTH_SETTING_ACTION);
+ launchIntent.addCategory(BluetoothPrefs.BLUETOOTH_SETTING_CATEGORY);
+ PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0,
+ launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ extras.putParcelable(ERROR_RESOLUTION_ACTION_INTENT, pendingIntent);
+ PlaybackStateCompat errorState = new PlaybackStateCompat.Builder()
+ .setErrorMessage(getString(R.string.bluetooth_disconnected))
+ .setExtras(extras)
+ .setState(PlaybackStateCompat.STATE_ERROR, 0, 0)
+ .build();
+ mSession.setPlaybackState(errorState);
+ }
+
@Override
public synchronized void onLoadChildren(final String parentMediaId,
- final Result<List<MediaItem>> result) {
+ final Result<List<MediaBrowserCompat.MediaItem>> result) {
if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
- List<MediaItem> contents = getContents(parentMediaId);
+ List<MediaBrowserCompat.MediaItem> contents =
+ MediaBrowserCompat.MediaItem.fromMediaItemList(getContents(parentMediaId));
if (contents == null) {
result.detach();
} else {
@@ -112,7 +141,8 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
mMediaQueue.clear();
if (songList != null) {
for (MediaItem song : songList) {
- mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(),
+ mMediaQueue.add(new MediaSessionCompat.QueueItem(
+ MediaDescriptionCompat.fromMediaDescription(song.getDescription()),
mMediaQueue.size()));
}
}
@@ -129,8 +159,11 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
}
}
- static synchronized void addressedPlayerChanged(MediaSession.Callback callback) {
+ static synchronized void addressedPlayerChanged(MediaSessionCompat.Callback callback) {
if (sBluetoothMediaBrowserService != null) {
+ if (callback == null) {
+ sBluetoothMediaBrowserService.setErrorPlaybackState();
+ }
sBluetoothMediaBrowserService.mSession.setCallback(callback);
} else {
Log.w(TAG, "addressedPlayerChanged Unavailable");
@@ -139,13 +172,14 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
static synchronized void trackChanged(MediaMetadata mediaMetadata) {
if (sBluetoothMediaBrowserService != null) {
- sBluetoothMediaBrowserService.mSession.setMetadata(mediaMetadata);
+ sBluetoothMediaBrowserService.mSession.setMetadata(
+ MediaMetadataCompat.fromMediaMetadata(mediaMetadata));
} else {
Log.w(TAG, "trackChanged Unavailable");
}
}
- static synchronized void notifyChanged(PlaybackState playbackState) {
+ static synchronized void notifyChanged(PlaybackStateCompat playbackState) {
Log.d(TAG, "notifyChanged PlaybackState" + playbackState);
if (sBluetoothMediaBrowserService != null) {
sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState);
@@ -181,19 +215,19 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
*/
public static synchronized int getPlaybackState() {
if (sBluetoothMediaBrowserService != null) {
- PlaybackState currentPlaybackState =
+ PlaybackStateCompat currentPlaybackState =
sBluetoothMediaBrowserService.mSession.getController().getPlaybackState();
if (currentPlaybackState != null) {
return currentPlaybackState.getState();
}
}
- return PlaybackState.STATE_ERROR;
+ return PlaybackStateCompat.STATE_ERROR;
}
/**
* Get object for controlling playback
*/
- public static synchronized MediaController.TransportControls getTransportControls() {
+ public static synchronized MediaControllerCompat.TransportControls getTransportControls() {
if (sBluetoothMediaBrowserService != null) {
return sBluetoothMediaBrowserService.mSession.getController().getTransportControls();
} else {
@@ -212,4 +246,16 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
Log.w(TAG, "setActive Unavailable");
}
}
+
+ /**
+ * Get Media session for updating state
+ */
+ public static synchronized MediaSessionCompat getSession() {
+ if (sBluetoothMediaBrowserService != null) {
+ return sBluetoothMediaBrowserService.mSession;
+ } else {
+ Log.w(TAG, "getSession Unavailable");
+ return null;
+ }
+ }
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index accea2a70..923282d34 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -100,7 +100,7 @@ public class BrowseTree {
}
BrowseNode getTrackFromNowPlayingList(int trackNumber) {
- return mNowPlayingNode.mChildren.get(trackNumber);
+ return mNowPlayingNode.getChild(trackNumber);
}
// Each node of the tree is represented by Folder ID, Folder Name and the children.
@@ -218,6 +218,13 @@ public class BrowseTree {
return mChildren;
}
+ synchronized BrowseNode getChild(int index) {
+ if (index < 0 || index >= mChildren.size()) {
+ return null;
+ }
+ return mChildren.get(index);
+ }
+
synchronized BrowseNode getParent() {
return mParent;
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
index c34a2d7d0..362548e5f 100644
--- a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
+++ b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
@@ -16,12 +16,11 @@
package com.android.bluetooth.avrcpcontroller;
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
+import android.util.SparseArray;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
/*
* Contains information Player Application Setting extended from BluetootAvrcpPlayerSettings
@@ -32,10 +31,10 @@ class PlayerApplicationSettings {
/*
* Values for SetPlayerApplicationSettings from AVRCP Spec V1.6 Appendix F.
*/
- private static final byte JNI_ATTRIB_EQUALIZER_STATUS = 0x01;
- private static final byte JNI_ATTRIB_REPEAT_STATUS = 0x02;
- private static final byte JNI_ATTRIB_SHUFFLE_STATUS = 0x03;
- private static final byte JNI_ATTRIB_SCAN_STATUS = 0x04;
+ static final byte EQUALIZER_STATUS = 0x01;
+ static final byte REPEAT_STATUS = 0x02;
+ static final byte SHUFFLE_STATUS = 0x03;
+ static final byte SCAN_STATUS = 0x04;
private static final byte JNI_EQUALIZER_STATUS_OFF = 0x01;
private static final byte JNI_EQUALIZER_STATUS_ON = 0x02;
@@ -55,18 +54,17 @@ class PlayerApplicationSettings {
private static final byte JNI_STATUS_INVALID = -1;
-
/*
* Hash map of current settings.
*/
- private Map<Integer, Integer> mSettings = new HashMap<Integer, Integer>();
+ private SparseArray<Integer> mSettings = new SparseArray<>();
/*
* Hash map of supported values, a setting should be supported by the remote in order to enable
* in mSettings.
*/
- private Map<Integer, ArrayList<Integer>> mSupportedValues =
- new HashMap<Integer, ArrayList<Integer>>();
+ private SparseArray<ArrayList<Integer>> mSupportedValues =
+ new SparseArray<ArrayList<Integer>>();
/* Convert from JNI array to Java classes. */
static PlayerApplicationSettings makeSupportedSettings(byte[] btAvrcpAttributeList) {
@@ -82,8 +80,7 @@ class PlayerApplicationSettings {
supportedValues.add(
mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
}
- newObj.mSupportedValues.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
- supportedValues);
+ newObj.mSupportedValues.put(attrId, supportedValues);
}
} catch (ArrayIndexOutOfBoundsException exception) {
Log.e(TAG, "makeSupportedSettings attributeList index error.");
@@ -91,25 +88,13 @@ class PlayerApplicationSettings {
return newObj;
}
- public BluetoothAvrcpPlayerSettings getAvrcpSettings() {
- int supportedSettings = 0;
- for (Integer setting : mSettings.keySet()) {
- supportedSettings |= setting;
- }
- BluetoothAvrcpPlayerSettings result = new BluetoothAvrcpPlayerSettings(supportedSettings);
- for (Integer setting : mSettings.keySet()) {
- result.addSettingValue(setting, mSettings.get(setting));
- }
- return result;
- }
-
static PlayerApplicationSettings makeSettings(byte[] btAvrcpAttributeList) {
PlayerApplicationSettings newObj = new PlayerApplicationSettings();
try {
for (int i = 0; i < btAvrcpAttributeList.length; ) {
byte attrId = btAvrcpAttributeList[i++];
- newObj.mSettings.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
+ newObj.mSettings.put(attrId,
mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
}
} catch (ArrayIndexOutOfBoundsException exception) {
@@ -123,177 +108,69 @@ class PlayerApplicationSettings {
mSupportedValues = updates.mSupportedValues;
}
- public void setValues(BluetoothAvrcpPlayerSettings updates) {
- int supportedSettings = updates.getSettings();
- for (int i = 1; i <= BluetoothAvrcpPlayerSettings.SETTING_SCAN; i++) {
- if ((i & supportedSettings) > 0) {
- mSettings.put(i, updates.getSettingValue(i));
- }
- }
+ public boolean supportsSetting(int settingType, int settingValue) {
+ if (null == mSupportedValues.get(settingType)) return false;
+ return mSupportedValues.valueAt(settingType).contains(settingValue);
}
- /*
- * Check through all settings to ensure that they are all available to be set and then check
- * that the desired value is in fact supported by our remote player.
- */
- public boolean supportsSettings(BluetoothAvrcpPlayerSettings settingsToCheck) {
- int settingSubset = settingsToCheck.getSettings();
- int supportedSettings = 0;
- for (Integer setting : mSupportedValues.keySet()) {
- supportedSettings |= setting;
- }
- try {
- if ((supportedSettings & settingSubset) == settingSubset) {
- for (Integer settingId : mSettings.keySet()) {
- // The setting is in both settings to check and supported settings but the
- // value is not supported.
- if ((settingId & settingSubset) == settingId && (!mSupportedValues.get(
- settingId).contains(settingsToCheck.getSettingValue(settingId)))) {
- return false;
- }
- }
- return true;
- }
- } catch (NullPointerException e) {
- Log.e(TAG,
- "supportsSettings received a supported setting that has no supported values.");
- }
- return false;
+ public boolean supportsSetting(int settingType) {
+ return (null != mSupportedValues.get(settingType));
}
- // Convert currently desired settings into an attribute array to pass to the native layer to
- // enable them.
- public ArrayList<Byte> getNativeSettings() {
- int i = 0;
- ArrayList<Byte> attribArray = new ArrayList<Byte>();
- for (Integer settingId : mSettings.keySet()) {
- switch (settingId) {
- case BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER:
- attribArray.add(JNI_ATTRIB_EQUALIZER_STATUS);
- attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
- mSettings.get(settingId)));
- break;
- case BluetoothAvrcpPlayerSettings.SETTING_REPEAT:
- attribArray.add(JNI_ATTRIB_REPEAT_STATUS);
- attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
- mSettings.get(settingId)));
- break;
- case BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE:
- attribArray.add(JNI_ATTRIB_SHUFFLE_STATUS);
- attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
- mSettings.get(settingId)));
- break;
- case BluetoothAvrcpPlayerSettings.SETTING_SCAN:
- attribArray.add(JNI_ATTRIB_SCAN_STATUS);
- attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
- mSettings.get(settingId)));
- break;
- default:
- Log.w(TAG, "Unknown setting found in getNativeSettings: " + settingId);
- }
- }
- return attribArray;
+ public int getSetting(int settingType) {
+ if (null == mSettings.get(settingType)) return -1;
+ return mSettings.get(settingType);
}
// Convert a native Attribute Id/Value pair into the AVRCP equivalent value.
private static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) {
- if (attribId == JNI_ATTRIB_EQUALIZER_STATUS) {
- switch (attribVal) {
- case JNI_EQUALIZER_STATUS_OFF:
- return BluetoothAvrcpPlayerSettings.STATE_OFF;
- case JNI_EQUALIZER_STATUS_ON:
- return BluetoothAvrcpPlayerSettings.STATE_ON;
- }
- } else if (attribId == JNI_ATTRIB_REPEAT_STATUS) {
+ if (attribId == REPEAT_STATUS) {
switch (attribVal) {
case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT:
- return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ return PlaybackStateCompat.REPEAT_MODE_ALL;
case JNI_REPEAT_STATUS_GROUP_REPEAT:
- return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ return PlaybackStateCompat.REPEAT_MODE_GROUP;
case JNI_REPEAT_STATUS_OFF:
- return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ return PlaybackStateCompat.REPEAT_MODE_NONE;
case JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT:
- return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
- }
- } else if (attribId == JNI_ATTRIB_SCAN_STATUS) {
- switch (attribVal) {
- case JNI_SCAN_STATUS_ALL_TRACK_SCAN:
- return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
- case JNI_SCAN_STATUS_GROUP_SCAN:
- return BluetoothAvrcpPlayerSettings.STATE_GROUP;
- case JNI_SCAN_STATUS_OFF:
- return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ return PlaybackStateCompat.REPEAT_MODE_ONE;
}
- } else if (attribId == JNI_ATTRIB_SHUFFLE_STATUS) {
+ } else if (attribId == SHUFFLE_STATUS) {
switch (attribVal) {
case JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
- return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ return PlaybackStateCompat.SHUFFLE_MODE_ALL;
case JNI_SHUFFLE_STATUS_GROUP_SHUFFLE:
- return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ return PlaybackStateCompat.SHUFFLE_MODE_GROUP;
case JNI_SHUFFLE_STATUS_OFF:
- return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ return PlaybackStateCompat.SHUFFLE_MODE_NONE;
}
}
- return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+ return JNI_STATUS_INVALID;
}
// Convert an AVRCP Setting/Value pair into the native equivalent value;
- private static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
- if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) {
- switch (mSettingVal) {
- case BluetoothAvrcpPlayerSettings.STATE_OFF:
- return JNI_EQUALIZER_STATUS_OFF;
- case BluetoothAvrcpPlayerSettings.STATE_ON:
- return JNI_EQUALIZER_STATUS_ON;
- }
- } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) {
+ static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
+ if (mSetting == REPEAT_STATUS) {
switch (mSettingVal) {
- case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ case PlaybackStateCompat.REPEAT_MODE_NONE:
return JNI_REPEAT_STATUS_OFF;
- case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
+ case PlaybackStateCompat.REPEAT_MODE_ONE:
return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT;
- case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ case PlaybackStateCompat.REPEAT_MODE_ALL:
return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT;
- case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ case PlaybackStateCompat.REPEAT_MODE_GROUP:
return JNI_REPEAT_STATUS_GROUP_REPEAT;
}
- } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
+ } else if (mSetting == SHUFFLE_STATUS) {
switch (mSettingVal) {
- case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ case PlaybackStateCompat.SHUFFLE_MODE_NONE:
return JNI_SHUFFLE_STATUS_OFF;
- case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ case PlaybackStateCompat.SHUFFLE_MODE_ALL:
return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
- case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ case PlaybackStateCompat.SHUFFLE_MODE_GROUP:
return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
}
- } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
- switch (mSettingVal) {
- case BluetoothAvrcpPlayerSettings.STATE_OFF:
- return JNI_SCAN_STATUS_OFF;
- case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
- return JNI_SCAN_STATUS_ALL_TRACK_SCAN;
- case BluetoothAvrcpPlayerSettings.STATE_GROUP:
- return JNI_SCAN_STATUS_GROUP_SCAN;
- }
}
return JNI_STATUS_INVALID;
}
-
- // convert a native Attribute Id into the AVRCP Setting equivalent value;
- private static int mapBTAttribIdToAvrcpPlayerSettings(byte attribId) {
- switch (attribId) {
- case JNI_ATTRIB_EQUALIZER_STATUS:
- return BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
- case JNI_ATTRIB_REPEAT_STATUS:
- return BluetoothAvrcpPlayerSettings.SETTING_REPEAT;
- case JNI_ATTRIB_SHUFFLE_STATUS:
- return BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE;
- case JNI_ATTRIB_SCAN_STATUS:
- return BluetoothAvrcpPlayerSettings.SETTING_SCAN;
- default:
- return BluetoothAvrcpPlayerSettings.STATE_INVALID;
- }
- }
-
}
-
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 009e42cef..a65bcfdf9 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -1559,7 +1559,6 @@ public class AdapterService extends Service {
if (service == null) {
return false;
}
- service.disable();
return service.factoryReset();
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 989043a7d..e92688f8c 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -1196,6 +1196,21 @@ public class HeadsetStateMachine extends StateMachine {
}
}
+ class MyAudioServerStateCallback extends AudioManager.AudioServerStateCallback {
+ @Override
+ public void onAudioServerDown() {
+ logi("onAudioServerDown");
+ }
+
+ @Override
+ public void onAudioServerUp() {
+ logi("onAudioServerUp restoring audio parameters");
+ setAudioParameters();
+ }
+ }
+
+ MyAudioServerStateCallback mAudioServerStateCallback = new MyAudioServerStateCallback();
+
class AudioOn extends ConnectedBase {
@Override
int getAudioStateInt() {
@@ -1214,10 +1229,21 @@ public class HeadsetStateMachine extends StateMachine {
mHeadsetService.setActiveDevice(mDevice);
}
setAudioParameters();
+
+ mSystemInterface.getAudioManager().setAudioServerStateCallback(
+ mHeadsetService.getMainExecutor(), mAudioServerStateCallback);
+
broadcastStateTransitions();
}
@Override
+ public void exit() {
+ super.exit();
+
+ mSystemInterface.getAudioManager().clearAudioServerStateCallback();
+ }
+
+ @Override
public boolean processMessage(Message message) {
switch (message.what) {
case CONNECT: {
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
index 05af73e08..b567371f1 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
@@ -224,7 +224,7 @@ public class HfpClientDeviceBlock {
if (DBG) {
Log.d(mTAG, "prevConn " + prevConn.isClosing() + " new call " + newCall.getState());
}
- if (prevConn.isClosing()
+ if (prevConn.isClosing() && prevConn.getCall().getState() != newCall.getState()
&& newCall.getState() != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
return true;
}
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index 0a428b418..9b86aaef1 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -42,7 +42,6 @@ package com.android.bluetooth.mapclient;
import android.app.Activity;
import android.app.PendingIntent;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothMapClient;
import android.bluetooth.BluetoothProfile;
@@ -59,6 +58,7 @@ import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.map.BluetoothMapbMessageMime;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IState;
import com.android.internal.util.State;
@@ -69,7 +69,6 @@ import com.android.vcard.VCardProperty;
import java.util.ArrayList;
import java.util.Calendar;
-import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -347,7 +346,9 @@ final class MceStateMachine extends StateMachine {
void setDefaultMessageType(SdpMasRecord sdpMasRecord) {
int supportedMessageTypes = sdpMasRecord.getSupportedMessageTypes();
synchronized (mDefaultMessageType) {
- if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) {
+ if ((supportedMessageTypes & SdpMasRecord.MessageType.MMS) > 0) {
+ mDefaultMessageType = Bmessage.Type.MMS;
+ } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) {
mDefaultMessageType = Bmessage.Type.SMS_CDMA;
} else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_GSM) > 0) {
mDefaultMessageType = Bmessage.Type.SMS_GSM;
@@ -473,6 +474,11 @@ final class MceStateMachine extends StateMachine {
}
break;
+ case MSG_MAS_DISCONNECTED:
+ deferMessage(message);
+ transitionTo(mDisconnecting);
+ break;
+
case MSG_OUTBOUND_MESSAGE:
mMasClient.makeRequest(
new RequestPushMessage(FOLDER_OUTBOX, (Bmessage) message.obj, null,
@@ -625,9 +631,12 @@ final class MceStateMachine extends StateMachine {
}
ArrayList<com.android.bluetooth.mapclient.Message> messageListing = request.getList();
if (messageListing != null) {
- for (com.android.bluetooth.mapclient.Message msg : messageListing) {
+ // Message listings by spec arrive ordered newest first but we wish to broadcast as
+ // oldest first. Iterate in reverse order so we initiate requests oldest first.
+ for (int i = messageListing.size() - 1; i >= 0; i--) {
+ com.android.bluetooth.mapclient.Message msg = messageListing.get(i);
if (DBG) {
- Log.d(TAG, "getting message ");
+ Log.d(TAG, "getting message for handle " + msg.getHandle());
}
// A message listing coming from the server should always have up to date data
mMessages.put(msg.getHandle(), new MessageMetadata(msg.getHandle(),
@@ -665,6 +674,7 @@ final class MceStateMachine extends StateMachine {
switch (message.getType()) {
case SMS_CDMA:
case SMS_GSM:
+ case MMS:
if (DBG) {
Log.d(TAG, "Body: " + message.getBodyContent());
}
@@ -705,6 +715,12 @@ final class MceStateMachine extends StateMachine {
intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME,
originator.getDisplayName());
}
+ if (message.getType() == Bmessage.Type.MMS) {
+ BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime();
+ mmsBmessage.parseMsgPart(message.getBodyContent());
+ intent.putExtra(android.content.Intent.EXTRA_TEXT,
+ mmsBmessage.getMessageAsText());
+ }
// Only send to the current default SMS app if one exists
String defaultMessagingPackage = Telephony.Sms.getDefaultSmsPackage(mService);
if (defaultMessagingPackage != null) {
@@ -712,8 +728,6 @@ final class MceStateMachine extends StateMachine {
}
mService.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
break;
-
- case MMS:
case EMAIL:
default:
Log.e(TAG, "Received unhandled type" + message.getType().toString());
diff --git a/src/com/android/bluetooth/mapclient/MnsObexServer.java b/src/com/android/bluetooth/mapclient/MnsObexServer.java
index 53cd79bb2..33ba1ea74 100644
--- a/src/com/android/bluetooth/mapclient/MnsObexServer.java
+++ b/src/com/android/bluetooth/mapclient/MnsObexServer.java
@@ -90,6 +90,10 @@ class MnsObexServer extends ServerRequestHandler {
if (VDBG) {
Log.v(TAG, "onDisconnect");
}
+ MceStateMachine currentStateMachine = mStateMachineReference.get();
+ if (currentStateMachine != null) {
+ currentStateMachine.disconnect();
+ }
}
@Override
diff --git a/src/com/android/bluetooth/mapclient/MnsService.java b/src/com/android/bluetooth/mapclient/MnsService.java
index c1ab39e00..b3317df90 100644
--- a/src/com/android/bluetooth/mapclient/MnsService.java
+++ b/src/com/android/bluetooth/mapclient/MnsService.java
@@ -17,6 +17,7 @@
package com.android.bluetooth.mapclient;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
@@ -129,6 +130,11 @@ public class MnsService {
Log.e(TAG, "Error: NO statemachine for device: " + device.getAddress()
+ " (name: " + device.getName());
return false;
+ } else if (stateMachine.getState() != BluetoothProfile.STATE_CONNECTED) {
+ Log.e(TAG, "Error: statemachine for device: " + device.getAddress()
+ + " (name: " + device.getName() + ") is not currently CONNECTED : "
+ + stateMachine.getCurrentState());
+ return false;
}
MnsObexServer srv = new MnsObexServer(stateMachine, sServerSockets);
BluetoothObexTransport transport = new BluetoothObexTransport(socket);
diff --git a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
index 2705e3429..5b844dce5 100644
--- a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
+++ b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
@@ -309,6 +309,12 @@ class BmessageParser {
String remng = mParser.remaining();
byte[] data = remng.getBytes();
+ if (offset < 0 || offset > data.length) {
+ /* Handle possible exception for incorrect LENGTH value
+ * from MSE while parsing end of props */
+ throw new ParseException("Invalid LENGTH value", mParser.pos());
+ }
+
/* restart parsing from after 'message'<CRLF> */
mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos);
diff --git a/src/com/android/bluetooth/mapclient/obex/ObexTime.java b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
index 42a32c10f..cc58a5144 100644
--- a/src/com/android/bluetooth/mapclient/obex/ObexTime.java
+++ b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
@@ -29,8 +29,17 @@ public final class ObexTime {
public ObexTime(String time) {
/*
- * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset
- * +/-hhmm
+ * Match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset +/-hhmm
+ *
+ * Matched groups are numberes as follows:
+ *
+ * YYYY MM DD T HH MM SS + hh mm
+ * ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^
+ * 1 2 3 4 5 6 8 9 10
+ * |---7---|
+ *
+ * All groups are guaranteed to be numeric so conversion will always succeed (except group 8
+ * which is either + or -)
*/
Pattern p = Pattern.compile(
"(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2})" + ")?");
@@ -39,20 +48,26 @@ public final class ObexTime {
if (m.matches()) {
/*
- * matched groups are numberes as follows: YYYY MM DD T HH MM SS +
- * hh mm ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ 1 2 3 4 5 6 8 9 10 all groups
- * are guaranteed to be numeric so conversion will always succeed
- * (except group 8 which is either + or -)
+ * MAP spec says to default to "Local Time basis" for a message listing timestamp. We'll
+ * use the system default timezone and assume it knows best what our local timezone is.
+ * The builder defaults to the default locale and timezone if none is provided.
*/
+ Calendar.Builder builder = new Calendar.Builder();
- Calendar cal = Calendar.getInstance();
- cal.set(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1,
- Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)),
- Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6)));
+ /* Note that Calendar months are zero-based */
+ builder.setDate(Integer.parseInt(m.group(1)), /* year */
+ Integer.parseInt(m.group(2)) - 1, /* month */
+ Integer.parseInt(m.group(3))); /* day of month */
+
+ /* Note the MAP timestamp doesn't have milliseconds and we're explicitly setting to 0 */
+ builder.setTimeOfDay(Integer.parseInt(m.group(4)), /* hours */
+ Integer.parseInt(m.group(5)), /* minutes */
+ Integer.parseInt(m.group(6)), /* seconds */
+ 0); /* milliseconds */
/*
- * if 7th group is matched then we have UTC offset information
- * included
+ * If 7th group is matched then we're no longer using "Local Time basis" and instead
+ * have a UTC based timestamp and offset information included
*/
if (m.group(7) != null) {
int ohh = Integer.parseInt(m.group(9));
@@ -68,10 +83,10 @@ public final class ObexTime {
TimeZone tz = TimeZone.getTimeZone("UTC");
tz.setRawOffset(offset);
- cal.setTimeZone(tz);
+ builder.setTimeZone(tz);
}
- mDate = cal.getTime();
+ mDate = builder.build().getTime();
}
}
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index aa5a1fd31..92eab7787 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -40,6 +40,7 @@ import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import java.net.InetAddress;
import java.util.ArrayList;
@@ -67,6 +68,9 @@ public class PanService extends ProfileService {
private String mNapIfaceAddr;
private boolean mNativeAvailable;
+ @VisibleForTesting
+ UserManager mUserManager;
+
private static final int MESSAGE_CONNECT = 1;
private static final int MESSAGE_DISCONNECT = 2;
private static final int MESSAGE_CONNECT_STATE_CHANGED = 11;
@@ -116,6 +120,8 @@ public class PanService extends ProfileService {
initializeNative();
mNativeAvailable = true;
+ mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
+
mNetworkFactory =
new BluetoothTetheringNetworkFactory(getBaseContext(), getMainLooper(), this);
setPanService(this);
@@ -137,6 +143,9 @@ public class PanService extends ProfileService {
cleanupNative();
mNativeAvailable = false;
}
+
+ mUserManager = null;
+
if (mPanDevices != null) {
int[] desiredStates = {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTING};
@@ -319,6 +328,10 @@ public class PanService extends ProfileService {
public boolean connect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (mUserManager.isGuestUser()) {
+ Log.w(TAG, "Guest user does not have the permission to change the WiFi network");
+ return false;
+ }
if (getConnectionState(device) != BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "Pan Device not disconnected: " + device);
return false;
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
index 34ab7d52c..a5b3fbfea 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -73,7 +73,7 @@ final class BluetoothPbapRequestPullPhoneBook extends BluetoothPbapRequest {
oap.add(OAP_TAGID_FORMAT, format);
/*
- * maxListCount is a special case which is handled in
+ * maxListCount == 0 is a special case which is handled in
* BluetoothPbapRequestPullPhoneBookSize
*/
if (maxListCount > 0) {
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
new file mode 100644
index 000000000..f0706610f
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullPhoneBookSize extends BluetoothPbapRequest {
+
+ private static final boolean VDBG = Utils.VDBG;
+
+ private static final String TAG = "BtPbapReqPullPhoneBookSize";
+
+ private static final String TYPE = "x-bt/phonebook";
+
+ private int mSize;
+
+ BluetoothPbapRequestPullPhoneBookSize(String pbName, long filter) {
+ mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+ mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+ ObexAppParameters oap = new ObexAppParameters();
+ // Set MaxListCount in the request to 0 to get PhonebookSize in the response.
+ // If a vCardSelector is present in the request, then the result shall
+ // contain the number of items that satisfy the selector’s criteria.
+ // See PBAP v1.2.3, Sec. 5.1.4.5.
+ oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+ if (filter != 0) {
+ oap.add(OAP_TAGID_FILTER, filter);
+ }
+ oap.addToHeaderSet(mHeaderSet);
+ }
+
+ @Override
+ protected void readResponseHeaders(HeaderSet headerset) {
+ if (VDBG) {
+ Log.v(TAG, "readResponseHeaders");
+ }
+
+ ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+ if (oap.exists(OAP_TAGID_PHONEBOOK_SIZE)) {
+ mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+ }
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 914b5b163..4e4a240f1 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -32,8 +32,10 @@ import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.R;
+import com.android.vcard.VCardEntry;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
import javax.obex.ClientSession;
@@ -46,6 +48,15 @@ import javax.obex.ResponseCodes;
* controlling state machine.
*/
class PbapClientConnectionHandler extends Handler {
+ // Tradeoff: larger BATCH_SIZE leads to faster download rates, while smaller
+ // BATCH_SIZE is less prone to IO Exceptions if there is a download in
+ // progress when Bluetooth stack is torn down.
+ private static final int DEFAULT_BATCH_SIZE = 250;
+
+ // Upper limit on the indices of the vcf cards/entries, inclusive,
+ // i.e., valid indices are [0, 1, ... , UPPER_LIMIT]
+ private static final int UPPER_LIMIT = 65535;
+
static final String TAG = "PbapClientConnHandler";
static final boolean DBG = Utils.DBG;
static final boolean VDBG = Utils.VDBG;
@@ -88,16 +99,26 @@ class PbapClientConnectionHandler extends Handler {
private static final long PBAP_FILTER_NICKNAME = 1 << 23;
private static final int PBAP_SUPPORTED_FEATURE =
- PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING;
+ PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_DOWNLOADING;
private static final long PBAP_REQUESTED_FIELDS =
PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
| PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
private static final int L2CAP_INVALID_PSM = -1;
public static final String PB_PATH = "telecom/pb.vcf";
+ public static final String FAV_PATH = "telecom/fav.vcf";
public static final String MCH_PATH = "telecom/mch.vcf";
public static final String ICH_PATH = "telecom/ich.vcf";
public static final String OCH_PATH = "telecom/och.vcf";
+ public static final String SIM_PB_PATH = "SIM1/telecom/pb.vcf";
+ public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf";
+ public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf";
+ public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf";
+
+ // PBAP v1.2.3 Sec. 7.1.2
+ private static final int SUPPORTED_REPOSITORIES_LOCALPHONEBOOK = 1 << 0;
+ private static final int SUPPORTED_REPOSITORIES_SIMCARD = 1 << 1;
+ private static final int SUPPORTED_REPOSITORIES_FAVORITES = 1 << 3;
public static final int PBAP_V1_2 = 0x0102;
public static final byte VCARD_TYPE_21 = 0;
@@ -239,29 +260,25 @@ class PbapClientConnectionHandler extends Handler {
break;
case MSG_DOWNLOAD:
- try {
- mAccountCreated = addAccount(mAccount);
- if (!mAccountCreated) {
- Log.e(TAG, "Account creation failed.");
- return;
- }
- // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2
- BluetoothPbapRequestPullPhoneBook request =
- new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount,
- PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, 0, 1);
- request.execute(mObexSession);
- PhonebookPullRequest processor =
- new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
- mAccount);
- processor.setResults(request.getList());
- processor.onPullComplete();
- HashMap<String, Integer> callCounter = new HashMap<>();
- downloadCallLog(MCH_PATH, callCounter);
- downloadCallLog(ICH_PATH, callCounter);
- downloadCallLog(OCH_PATH, callCounter);
- } catch (IOException e) {
- Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString());
+ mAccountCreated = addAccount(mAccount);
+ if (!mAccountCreated) {
+ Log.e(TAG, "Account creation failed.");
+ return;
}
+ if (isRepositorySupported(SUPPORTED_REPOSITORIES_FAVORITES)) {
+ downloadContacts(FAV_PATH);
+ }
+ if (isRepositorySupported(SUPPORTED_REPOSITORIES_LOCALPHONEBOOK)) {
+ downloadContacts(PB_PATH);
+ }
+ if (isRepositorySupported(SUPPORTED_REPOSITORIES_SIMCARD)) {
+ downloadContacts(SIM_PB_PATH);
+ }
+
+ HashMap<String, Integer> callCounter = new HashMap<>();
+ downloadCallLog(MCH_PATH, callCounter);
+ downloadCallLog(ICH_PATH, callCounter);
+ downloadCallLog(OCH_PATH, callCounter);
break;
default:
@@ -369,6 +386,59 @@ class PbapClientConnectionHandler extends Handler {
}
}
+ void downloadContacts(String path) {
+ try {
+ PhonebookPullRequest processor =
+ new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
+ mAccount);
+
+ // Download contacts in batches of size DEFAULT_BATCH_SIZE
+ BluetoothPbapRequestPullPhoneBookSize requestPbSize =
+ new BluetoothPbapRequestPullPhoneBookSize(path,
+ PBAP_REQUESTED_FIELDS);
+ requestPbSize.execute(mObexSession);
+
+ int numberOfContactsRemaining = requestPbSize.getSize();
+ int startOffset = 0;
+ if (PB_PATH.equals(path)) {
+ // PBAP v1.2.3, Sec 3.1.5. The first contact in pb is owner card 0.vcf, which we
+ // do not want to download. The other phonebook objects (e.g., fav) don't have an
+ // owner card, so they don't need an offset.
+ startOffset = 1;
+ // "-1" because Owner Card 0.vcf is also included in /pb, but not in /fav.
+ numberOfContactsRemaining -= 1;
+ }
+
+ while ((numberOfContactsRemaining > 0) && (startOffset <= UPPER_LIMIT)) {
+ int numberOfContactsToDownload =
+ Math.min(Math.min(DEFAULT_BATCH_SIZE, numberOfContactsRemaining),
+ UPPER_LIMIT - startOffset + 1);
+ BluetoothPbapRequestPullPhoneBook request =
+ new BluetoothPbapRequestPullPhoneBook(path, mAccount,
+ PBAP_REQUESTED_FIELDS, VCARD_TYPE_30,
+ numberOfContactsToDownload, startOffset);
+ request.execute(mObexSession);
+ ArrayList<VCardEntry> vcards = request.getList();
+ if (path == FAV_PATH) {
+ // mark each vcard as a favorite
+ for (VCardEntry v : vcards) {
+ v.setStarred(true);
+ }
+ }
+ processor.setResults(vcards);
+ processor.onPullComplete();
+
+ startOffset += numberOfContactsToDownload;
+ numberOfContactsRemaining -= numberOfContactsToDownload;
+ }
+ if ((startOffset > UPPER_LIMIT) && (numberOfContactsRemaining > 0)) {
+ Log.w(TAG, "Download contacts incomplete, index exceeded upper limit.");
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Download contacts failure" + e.toString());
+ }
+ }
+
void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
try {
BluetoothPbapRequestPullPhoneBook request =
@@ -419,4 +489,12 @@ class PbapClientConnectionHandler extends Handler {
Log.d(TAG, "Call Logs could not be deleted, they may not exist yet.");
}
}
+
+ private boolean isRepositorySupported(int mask) {
+ if (mPseRec == null) {
+ if (VDBG) Log.v(TAG, "No PBAP Server SDP Record");
+ return false;
+ }
+ return (mask & mPseRec.getSupportedRepositories()) != 0;
+ }
}
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index f150cddec..02b1e7a51 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -19,9 +19,11 @@ package com.android.bluetooth.pbapclient;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothPbapClient;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -31,6 +33,7 @@ import android.util.Log;
import com.android.bluetooth.R;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
import com.android.bluetooth.sdp.SdpManager;
import java.util.ArrayList;
@@ -71,6 +74,9 @@ public class PbapClientService extends ProfileService {
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
// delay initial download until after the user is unlocked to add an account.
filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ // To remove call logs when PBAP was never connected while calls were made,
+ // we also listen for HFP to become disconnected.
+ filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
try {
registerReceiver(mPbapBroadcastReceiver, filter);
} catch (Exception e) {
@@ -128,6 +134,21 @@ public class PbapClientService extends ProfileService {
}
}
+ private void removeHfpCallLog(String accountName, Context context) {
+ if (DBG) Log.d(TAG, "Removing call logs from " + accountName);
+ // Delete call logs belonging to accountName==BD_ADDR that also match
+ // component name "hfpclient".
+ ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class);
+ String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND "
+ + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?";
+ String[] selectionArgs = new String[]{accountName, componentName.flattenToString()};
+ try {
+ getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
+ }
+ }
+
private void registerSdpRecord() {
SdpManager sdpManager = SdpManager.getDefaultManager();
if (sdpManager == null) {
@@ -171,6 +192,21 @@ public class PbapClientService extends ProfileService {
for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
stateMachine.resumeDownload();
}
+ } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) {
+ // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects.
+ // However, if PBAP was never connected/enabled in the first place, and calls are
+ // made over HFP, these calllogs will not be removed when the device disconnects.
+ // This code ensures callogs are still removed in this case.
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+ if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ if (DBG) {
+ Log.d(TAG, "Received intent to disconnect HFP with " + device);
+ }
+ // HFP client stores entries in calllog.db by BD_ADDR and component name
+ removeHfpCallLog(device.getAddress(), context);
+ }
}
}
}