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();
         }