summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKevin F. Haggerty <haggertk@lineageos.org>2020-06-01 20:43:45 -0600
committerKevin F. Haggerty <haggertk@lineageos.org>2020-06-01 20:43:45 -0600
commitcf38327eba534b6354b44e4d29a3fef8190b481e (patch)
tree84195c87dacf64c2d7df6be202f9f62a62258aba /src
parent837b47f2b5613cb2e6235229831f9c80ec28c741 (diff)
parent906df07061fcf46aac856894fb6d68b2ccccfa2c (diff)
downloadandroid_packages_apps_Bluetooth-lineage-17.1.tar.gz
android_packages_apps_Bluetooth-lineage-17.1.tar.bz2
android_packages_apps_Bluetooth-lineage-17.1.zip
Merge tag 'android-10.0.0_r37' into staging/lineage-17.1_merge-android-10.0.0_r37lineage-17.1
Android 10.0.0 Release 37 (QQ3A.200605.001) * tag 'android-10.0.0_r37': (37 commits) Import translations. DO NOT MERGE Revert "PBAP server, send favorite contacts" Import translations. DO NOT MERGE Delete call logs when calls are made without PBAP Set Browsing bit to 0 in PbapSupportedFeatures Prevent phone's bd_addr from appearing in Accounts Return an empty list when the requested node is not in the tree AvrcpController Test update AVRCP Controller Media Controller not ready Allow subsequent requests for media keys to replay the silent audio sample HFP Client call status update PBAP server, send favorite contacts AVRCP Controller Disable Automatic Focus Request A2DP Sink: Focus gain while transient loss AVRCP Controller State without Browsing Add bluetooth prefs for Android Automotive MAP Client disconnect state machine if MAS client disconnected MAP Client close connection on MNS disconnect MAP Client Only connect MNS in connected state MAP Client BMessage parser length ... Change-Id: I1398f4ffe79e4ac0a37765927e4afd2e05ee0908
Diffstat (limited to 'src')
-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);
+ }
}
}
}