Snap for 4384531 from 974753ca4bfe9d296bfd838d3f1f3bdeb48928cd to oc-m2-release
Change-Id: I7dec3a9ea5f36e9a495f13f742bb8d491c461e00
diff --git a/res/values/config.xml b/res/values/config.xml
index b95417b..05448c5 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -58,11 +58,15 @@
<bool name="headset_client_initial_audio_route_allowed">true</bool>
- <!-- For AVRCP absolute volume feature. If the threshold is non-zero,
- restrict the initial volume to the threshold.
- Valid value is 1-14, and recommended value is 8 -->
+ <!-- @deprecated: use a2dp_absolute_volume_initial_threshold_percent
+ instead. -->
<integer name="a2dp_absolute_volume_initial_threshold">8</integer>
+ <!-- AVRCP absolute volume initial value as percent of the maximum value.
+ Valid values are in the interval [0, 100].
+ Recommended value is 50. -->
+ <integer name="a2dp_absolute_volume_initial_threshold_percent">50</integer>
+
<!-- For A2DP sink ducking volume feature. -->
<integer name="a2dp_sink_duck_percent">25</integer>
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index 274c10b..1b65597 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -33,6 +33,7 @@
import android.content.res.Resources;
import android.content.SharedPreferences;
import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser;
@@ -78,6 +79,8 @@
private Context mContext;
private final AudioManager mAudioManager;
private AvrcpMessageHandler mHandler;
+ private Handler mAudioManagerPlaybackHandler;
+ private AudioManagerPlaybackListener mAudioManagerPlaybackCb;
private MediaSessionManager mMediaSessionManager;
private @Nullable MediaController mMediaController;
private MediaControllerListener mMediaControllerCb;
@@ -87,6 +90,7 @@
private int mTransportControlFlags;
private @NonNull PlaybackState mCurrentPlayState;
private int mA2dpState;
+ private boolean mAudioManagerIsPlaying;
private int mPlayStatusChangedNT;
private byte mReportedPlayStatus;
private int mTrackChangedNT;
@@ -240,6 +244,7 @@
mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
mReportedPlayStatus = PLAYSTATUS_ERROR;
mA2dpState = BluetoothA2dp.STATE_NOT_PLAYING;
+ mAudioManagerIsPlaying = false;
mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
@@ -279,6 +284,13 @@
Resources resources = context.getResources();
if (resources != null) {
mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
+
+ // Update the threshold if the threshold_percent is valid
+ int threshold_percent =
+ resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold_percent);
+ if (threshold_percent >= 0 && threshold_percent <= 100) {
+ mAbsVolThreshold = (threshold_percent * mAudioStreamMax) / 100;
+ }
}
// Register for package removal intent broadcasts for media button receiver persistence
@@ -300,6 +312,8 @@
thread.start();
Looper looper = thread.getLooper();
mHandler = new AvrcpMessageHandler(looper);
+ mAudioManagerPlaybackHandler = new Handler(looper);
+ mAudioManagerPlaybackCb = new AudioManagerPlaybackListener();
mMediaControllerCb = new MediaControllerListener();
mAvrcpMediaRsp = new AvrcpMediaRsp();
mMediaPlayerInfoList = new TreeMap<Integer, MediaPlayerInfo>();
@@ -329,6 +343,9 @@
// initialize browsable player list and build media player list
buildBrowsablePlayerList();
}
+
+ mAudioManager.registerAudioPlaybackCallback(
+ mAudioManagerPlaybackCb, mAudioManagerPlaybackHandler);
}
public static Avrcp make(Context context) {
@@ -340,18 +357,23 @@
public synchronized void doQuit() {
if (DEBUG) Log.d(TAG, "doQuit");
+ if (mAudioManager != null) {
+ mAudioManager.unregisterAudioPlaybackCallback(mAudioManagerPlaybackCb);
+ }
if (mMediaController != null) mMediaController.unregisterCallback(mMediaControllerCb);
if (mMediaSessionManager != null) {
mMediaSessionManager.setCallback(null, null);
mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionListener);
}
+ mAudioManagerPlaybackHandler.removeCallbacksAndMessages(null);
mHandler.removeCallbacksAndMessages(null);
Looper looper = mHandler.getLooper();
if (looper != null) {
looper.quit();
}
+ mAudioManagerPlaybackHandler = null;
mHandler = null;
mContext.unregisterReceiver(mAvrcpReceiver);
mContext.unregisterReceiver(mBootReceiver);
@@ -367,6 +389,30 @@
mVolumeMapping.clear();
}
+ private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback {
+ @Override
+ public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+ super.onPlaybackConfigChanged(configs);
+ boolean isPlaying = false;
+ for (AudioPlaybackConfiguration config : configs) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "AudioManager Player: "
+ + AudioPlaybackConfiguration.toLogFriendlyString(config));
+ }
+ if (config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ isPlaying = true;
+ break;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "AudioManager isPlaying: " + isPlaying);
+ if (mAudioManagerIsPlaying != isPlaying) {
+ mAudioManagerIsPlaying = isPlaying;
+ updateCurrentMediaState();
+ }
+ }
+ }
+
private class MediaControllerListener extends MediaController.Callback {
@Override
public void onMetadataChanged(MediaMetadata metadata) {
@@ -413,10 +459,13 @@
case MSG_NATIVE_REQ_GET_RC_FEATURES:
{
String address = (String) msg.obj;
- if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_RC_FEATURES: address="+address+
- ", features="+msg.arg1);
mFeatures = msg.arg1;
mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address);
+ if (DEBUG) {
+ Log.v(TAG,
+ "MSG_NATIVE_REQ_GET_RC_FEATURES: address=" + address
+ + ", features=" + msg.arg1 + ", mFeatures=" + mFeatures);
+ }
mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
mLastLocalVolume = -1;
mRemoteVolume = -1;
@@ -797,10 +846,22 @@
if (controllerState != null) {
newState = controllerState;
- } else if (mAudioManager != null && mAudioManager.isMusicActive()) {
- // Use A2DP state if we don't have a state from MediaControlller
+ }
+ // Use the AudioManager to update the playback state.
+ // NOTE: We cannot use the
+ // (mA2dpState == BluetoothA2dp.STATE_PLAYING)
+ // check, because after Pause, the A2DP state remains in
+ // STATE_PLAYING for 3 more seconds.
+ // As a result of that, if we pause the music, on carkits the
+ // Play status indicator will continue to display "Playing"
+ // for 3 more seconds which can be confusing.
+ if (mAudioManagerIsPlaying
+ || (controllerState == null && mAudioManager != null
+ && mAudioManager.isMusicActive())) {
+ // Use AudioManager playback state if we don't have the state
+ // from MediaControlller
PlaybackState.Builder builder = new PlaybackState.Builder();
- if (mA2dpState == BluetoothA2dp.STATE_PLAYING) {
+ if (mAudioManagerIsPlaying) {
builder.setState(PlaybackState.STATE_PLAYING,
PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
} else {
@@ -1016,22 +1077,6 @@
mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
}
- if ((newQueueId == -1 || newQueueId != mLastQueueId)
- && currentAttributes.equals(mMediaAttributes)
- && newPlayStatus == PLAYSTATUS_PLAYING
- && mReportedPlayStatus == PLAYSTATUS_STOPPED) {
- // Most carkits like seeing the track changed before the
- // playback state changed, but some controllers are slow
- // to update their metadata. Hold of on sending the playback state
- // update until after we know the current metadata is up to date
- // and track changed has been sent. This was seen on BMW carkits
- Log.i(TAG,
- "Waiting for metadata update to send track changed: " + newQueueId + " : "
- + currentAttributes + " : " + mMediaAttributes);
-
- return;
- }
-
// Notify track changed if:
// - The CT is registered for the notification
// - Queue ID is UNKNOWN and MediaMetadata is different
@@ -1050,9 +1095,11 @@
}
// still send the updated play state if the playback state is none or buffering
- Log.e(TAG, "play status change " + mReportedPlayStatus + "➡" + newPlayStatus);
+ Log.e(TAG,
+ "play status change " + mReportedPlayStatus + "➡" + newPlayStatus
+ + " mPlayStatusChangedNT: " + mPlayStatusChangedNT);
if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
- && (mReportedPlayStatus != newPlayStatus)) {
+ || (mReportedPlayStatus != newPlayStatus)) {
sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
}
@@ -2364,6 +2411,20 @@
ProfileService.println(sb, " " + log);
}
}
+
+ // Print the blacklisted devices (for absolute volume control)
+ SharedPreferences pref =
+ mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
+ Map<String, ?> allKeys = pref.getAll();
+ ProfileService.println(sb, "");
+ ProfileService.println(sb, "Runtime Blacklisted Devices (absolute volume):");
+ if (allKeys.isEmpty()) {
+ ProfileService.println(sb, " None");
+ } else {
+ for (String key : allKeys.keySet()) {
+ ProfileService.println(sb, " " + key);
+ }
+ }
}
public class AvrcpBrowseManager {
@@ -2640,12 +2701,8 @@
return KeyEvent.KEYCODE_VOLUME_DOWN;
case BluetoothAvrcp.PASSTHROUGH_ID_MUTE:
return KeyEvent.KEYCODE_MUTE;
- case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
- return KeyEvent.KEYCODE_MEDIA_PLAY;
case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
return KeyEvent.KEYCODE_MEDIA_STOP;
- case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
- return KeyEvent.KEYCODE_MEDIA_PAUSE;
case BluetoothAvrcp.PASSTHROUGH_ID_RECORD:
return KeyEvent.KEYCODE_MEDIA_RECORD;
case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
@@ -2668,6 +2725,12 @@
return KeyEvent.KEYCODE_F4;
case BluetoothAvrcp.PASSTHROUGH_ID_F5:
return KeyEvent.KEYCODE_F5;
+ // Interop workaround for headphones/car kits
+ // which do not properly key track of playback
+ // state...
+ case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
+ case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
+ return KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
// Fallthrough for all unknown key mappings
case BluetoothAvrcp.PASSTHROUGH_ID_SELECT:
case BluetoothAvrcp.PASSTHROUGH_ID_ROOT_MENU:
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index a25a6f6..07b7bfe 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -65,8 +65,8 @@
private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices = new CopyOnWriteArrayList<BluetoothDevice>();
private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;
- private HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState;
-
+ private final HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState =
+ new HashMap<>();
private volatile int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
private volatile int mState = BluetoothAdapter.STATE_OFF;
@@ -156,11 +156,7 @@
mAdapter = BluetoothAdapter.getDefaultAdapter();
}
public void init(RemoteDevices remoteDevices) {
- if (mProfileConnectionState ==null) {
- mProfileConnectionState = new HashMap<Integer, Pair<Integer, Integer>>();
- } else {
- mProfileConnectionState.clear();
- }
+ mProfileConnectionState.clear();
mRemoteDevices = remoteDevices;
IntentFilter filter = new IntentFilter();
@@ -182,10 +178,7 @@
public void cleanup() {
mRemoteDevices = null;
- if (mProfileConnectionState != null) {
- mProfileConnectionState.clear();
- mProfileConnectionState = null;
- }
+ mProfileConnectionState.clear();
if (mReceiverRegistered) {
mService.unregisterReceiver(mReceiver);
mReceiverRegistered = false;
@@ -762,12 +755,17 @@
}
void onBluetoothReady() {
- Log.d(TAG, "ScanMode = " + mScanMode );
- Log.d(TAG, "State = " + getState() );
+ debugLog("onBluetoothReady, state=" + getState() + ", ScanMode=" + mScanMode);
- // When BT is being turned on, all adapter properties will be sent in 1
- // callback. At this stage, set the scan mode.
synchronized (mObject) {
+ // Reset adapter and profile connection states
+ setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
+ mProfileConnectionState.clear();
+ mProfilesConnected = 0;
+ mProfilesConnecting = 0;
+ mProfilesDisconnecting = 0;
+ // When BT is being turned on, all adapter properties will be sent in 1
+ // callback. At this stage, set the scan mode.
if (getState() == BluetoothAdapter.STATE_TURNING_ON &&
mScanMode == BluetoothAdapter.SCAN_MODE_NONE) {
/* mDiscoverableTimeout is part of the
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 7c404d9..e04ca8b 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -1499,6 +1499,7 @@
}
boolean startDiscovery() {
+ debugLog("startDiscovery");
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH ADMIN permission");
@@ -1506,6 +1507,7 @@
}
boolean cancelDiscovery() {
+ debugLog("cancelDiscovery");
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH ADMIN permission");
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index e3cee20..7e1164e 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -437,12 +437,16 @@
mWakeLock = null;
}
+ // Step 1: clean up active server session
if (mServerSession != null) {
mServerSession.close();
mServerSession = null;
}
-
+ // Step 2: clean up existing connection socket
closeConnectionSocket();
+ // Step 3: clean up SDP record
+ cleanUpSdpRecord();
+ // Step 4: clean up existing server socket(s)
closeServerSocket();
if (mServerSockets != null) {
mServerSockets.shutdown(false);
@@ -452,6 +456,24 @@
if (VERBOSE) Log.v(TAG, "Pbap Service closeService out");
}
+ private void cleanUpSdpRecord() {
+ if (mSdpHandle < 0) {
+ if (VERBOSE) Log.v(TAG, "cleanUpSdpRecord, SDP record never created");
+ return;
+ }
+ int sdpHandle = mSdpHandle;
+ mSdpHandle = -1;
+ SdpManager sdpManager = SdpManager.getDefaultManager();
+ if (sdpManager == null) {
+ Log.e(TAG, "cleanUpSdpRecord failed, sdpManager is null, sdpHandle=" + sdpHandle);
+ return;
+ }
+ Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
+ if (!sdpManager.removeSdpRecord(sdpHandle)) {
+ Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
+ }
+ }
+
private final void startObexServerSession() throws IOException {
if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
@@ -939,6 +961,10 @@
}
}
+ /**
+ * Start server side socket listeners. Caller should make sure that adapter is in a ready state
+ * and SDP record is cleaned up. Otherwise, this method will fail.
+ */
synchronized private void startSocketListeners() {
if (DEBUG) Log.d(TAG, "startsocketListener");
if (mServerSession != null) {
@@ -956,17 +982,10 @@
Log.e(TAG, "Failed to start the listeners");
return;
}
- SdpManager sdpManager = SdpManager.getDefaultManager();
- if (sdpManager == null) {
- Log.e(TAG, "Failed to start the listeners sdp null ");
+ if (mSdpHandle >= 0) {
+ Log.e(TAG, "SDP handle was not cleaned up, mSdpHandle=" + mSdpHandle);
return;
}
- if (mAdapter != null && mSdpHandle >= 0) {
- Log.d(TAG, "Removing SDP record for PBAP with SDP handle:" + mSdpHandle);
- boolean status = sdpManager.removeSdpRecord(mSdpHandle);
- Log.d(TAG, "RemoveSDPrecord returns " + status);
- mSdpHandle = -1;
- }
mSdpHandle = SdpManager.getDefaultManager().createPbapPseRecord(
"OBEX Phonebook Access Server", mServerSockets.getRfcommChannel(),
mServerSockets.getL2capPsm(), SDP_PBAP_SERVER_VERSION,
@@ -1057,8 +1076,13 @@
*/
@Override
public synchronized void onAcceptFailed() {
+ // Clean up SDP record first
+ cleanUpSdpRecord();
// Force socket listener to restart
- mServerSockets = null;
+ if (mServerSockets != null) {
+ mServerSockets.shutdown(false);
+ mServerSockets = null;
+ }
if (!mInterrupted && mAdapter != null && mAdapter.isEnabled()) {
startSocketListeners();
}