Merge "Fix bonded address been removed unexpected"
diff --git a/res/raw/silent.wav b/res/raw/silent.wav
new file mode 100755
index 0000000..d6d93ef
--- /dev/null
+++ b/res/raw/silent.wav
Binary files differ
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 4de89df..918c2ce 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -17,13 +17,17 @@
 package com.android.bluetooth.a2dp;
 
 import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.util.Log;
 
 import com.android.bluetooth.R;
 
+import java.util.Arrays;
+import java.util.Objects;
 /*
  * A2DP Codec Configuration setup.
  */
@@ -52,13 +56,47 @@
     }
 
     void setCodecConfigPreference(BluetoothDevice device,
+                                  BluetoothCodecStatus codecStatus,
                                   BluetoothCodecConfig codecConfig) {
+        Objects.requireNonNull(codecStatus);
+
+        // Check whether the codecConfig is selectable for this Bluetooth device.
+        BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities();
+        if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec ->
+                codec.isMandatoryCodec())) {
+            // Do not set codec preference to native if the selectableCodecs not contain mandatory
+            // codec. The reason could be remote codec negotiation is not completed yet.
+            Log.w(TAG, "Cannot find mandatory codec in selectableCodecs.");
+            return;
+        }
+        if (!isCodecConfigSelectable(codecConfig, selectableCodecs)) {
+            Log.w(TAG, "Codec is not selectable: " + codecConfig);
+            return;
+        }
+
+        // Check whether the codecConfig would change current codec config.
+        int prioritizedCodecType = getPrioitizedCodecType(codecConfig, selectableCodecs);
+        BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
+        if (prioritizedCodecType == currentCodecConfig.getCodecType()
+                && (currentCodecConfig.getCodecType() != codecConfig.getCodecType()
+                || currentCodecConfig.sameAudioFeedingParameters(codecConfig))) {
+            // Same codec with same parameters, no need to send this request to native.
+            Log.i(TAG, "setCodecConfigPreference: codec not changed.");
+            return;
+        }
+
         BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
         codecConfigArray[0] = codecConfig;
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
-    void enableOptionalCodecs(BluetoothDevice device) {
+    void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+        if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) {
+            Log.i(TAG, "enableOptionalCodecs: already using optional codec: "
+                    + currentCodecConfig.getCodecType());
+            return;
+        }
+
         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
         if (codecConfigArray == null) {
             return;
@@ -75,7 +113,12 @@
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
-    void disableOptionalCodecs(BluetoothDevice device) {
+    void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+        if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) {
+            Log.i(TAG, "disableOptionalCodecs: already using mandatory codec");
+            return;
+        }
+
         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
         if (codecConfigArray == null) {
             return;
@@ -92,6 +135,35 @@
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
+    // Get the codec type of the highest priority of selectableCodecs and codecConfig.
+    private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig,
+            BluetoothCodecConfig[] selectableCodecs) {
+        BluetoothCodecConfig prioritizedCodecConfig = codecConfig;
+        for (BluetoothCodecConfig config : selectableCodecs) {
+            if (prioritizedCodecConfig == null) {
+                prioritizedCodecConfig = config;
+            }
+            if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) {
+                prioritizedCodecConfig = config;
+            }
+        }
+        return prioritizedCodecConfig.getCodecType();
+    }
+
+    // Check whether the codecConfig is selectable
+    private static boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig,
+            BluetoothCodecConfig[] selectableCodecs) {
+        for (BluetoothCodecConfig config : selectableCodecs) {
+            if (codecConfig.getCodecType() == config.getCodecType()
+                    && (codecConfig.getSampleRate() & config.getSampleRate()) != 0
+                    && (codecConfig.getBitsPerSample() & config.getBitsPerSample()) != 0
+                    && (codecConfig.getChannelMode() & config.getChannelMode()) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     // Assign the A2DP Source codec config priorities
     private BluetoothCodecConfig[] assignCodecConfigPriorities() {
         Resources resources = mContext.getResources();
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 810a7c0..085bbb1 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -719,7 +719,17 @@
             Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
             return;
         }
-        mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
+        if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+            Log.e(TAG, "Cannot set codec config preference: not supported");
+            return;
+        }
+
+        BluetoothCodecStatus codecStatus = getCodecStatus(device);
+        if (codecStatus == null) {
+            Log.e(TAG, "Codec status is null on " + device);
+            return;
+        }
+        mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig);
     }
 
     /**
@@ -741,7 +751,16 @@
             Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
             return;
         }
-        mA2dpCodecConfig.enableOptionalCodecs(device);
+        if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+            Log.e(TAG, "Cannot enable optional codecs: not supported");
+            return;
+        }
+        BluetoothCodecStatus codecStatus = getCodecStatus(device);
+        if (codecStatus == null) {
+            Log.e(TAG, "Cannot enable optional codecs: codec status is null");
+            return;
+        }
+        mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
     }
 
     /**
@@ -763,7 +782,16 @@
             Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
             return;
         }
-        mA2dpCodecConfig.disableOptionalCodecs(device);
+        if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+            Log.e(TAG, "Cannot disable optional codecs: not supported");
+            return;
+        }
+        BluetoothCodecStatus codecStatus = getCodecStatus(device);
+        if (codecStatus == null) {
+            Log.e(TAG, "Cannot disable optional codecs: codec status is null");
+            return;
+        }
+        mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
     }
 
     public int getSupportsOptionalCodecs(BluetoothDevice device) {
@@ -835,7 +863,8 @@
      * @param sameAudioFeedingParameters if true the audio feeding parameters
      * haven't been changed
      */
-    void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
+    @VisibleForTesting
+    public void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
                             boolean sameAudioFeedingParameters) {
         // Log codec config and capability metrics
         BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
@@ -987,9 +1016,17 @@
         }
     }
 
-    private void updateOptionalCodecsSupport(BluetoothDevice device) {
+
+    /**
+     * Update and initiate optional codec status change to native.
+     *
+     * @param device the device to change optional codec status
+     */
+    @VisibleForTesting
+    public void updateOptionalCodecsSupport(BluetoothDevice device) {
         int previousSupport = getSupportsOptionalCodecs(device);
         boolean supportsOptional = false;
+        boolean hasMandatoryCodec = false;
 
         synchronized (mStateMachines) {
             A2dpStateMachine sm = mStateMachines.get(device);
@@ -999,13 +1036,22 @@
             BluetoothCodecStatus codecStatus = sm.getCodecStatus();
             if (codecStatus != null) {
                 for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
-                    if (!config.isMandatoryCodec()) {
+                    if (config.isMandatoryCodec()) {
+                        hasMandatoryCodec = true;
+                    } else {
                         supportsOptional = true;
-                        break;
                     }
                 }
             }
         }
+        if (!hasMandatoryCodec) {
+            // Mandatory codec(SBC) is not selectable. It could be caused by the remote device
+            // select codec before native finish get codec capabilities. Stop use this codec
+            // status as the reference to support/enable optional codecs.
+            Log.i(TAG, "updateOptionalCodecsSupport: Mandatory codec is not selectable.");
+            return;
+        }
+
         if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
                 || supportsOptional != (previousSupport
                                     == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
@@ -1034,10 +1080,6 @@
         }
         synchronized (mStateMachines) {
             if (toState == BluetoothProfile.STATE_CONNECTED) {
-                // Each time a device connects, we want to re-check if it supports optional
-                // codecs (perhaps it's had a firmware update, etc.) and save that state if
-                // it differs from what we had saved before.
-                updateOptionalCodecsSupport(device);
                 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
             }
             // Set the active device if only one connected device is supported and it was connected
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 9c8a221..e0df8b2 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -89,7 +89,8 @@
 
     private A2dpService mA2dpService;
     private A2dpNativeInterface mA2dpNativeInterface;
-    private boolean mA2dpOffloadEnabled = false;
+    @VisibleForTesting
+    boolean mA2dpOffloadEnabled = false;
     private final BluetoothDevice mDevice;
     private boolean mIsPlaying = false;
     private BluetoothCodecStatus mCodecStatus;
@@ -467,6 +468,10 @@
 
             removeDeferredMessages(CONNECT);
 
+            // Each time a device connects, we want to re-check if it supports optional
+            // codecs (perhaps it's had a firmware update, etc.) and save that state if
+            // it differs from what we had saved before.
+            mA2dpService.updateOptionalCodecsSupport(mDevice);
             broadcastConnectionState(mConnectionState, mLastConnectionState);
             // Upon connected, the audio starts out as stopped
             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
@@ -608,8 +613,11 @@
     }
 
     // NOTE: This event is processed in any state
-    private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
+    @VisibleForTesting
+    void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
         BluetoothCodecConfig prevCodecConfig = null;
+        BluetoothCodecStatus prevCodecStatus = mCodecStatus;
+
         synchronized (this) {
             if (mCodecStatus != null) {
                 prevCodecConfig = mCodecStatus.getCodecConfig();
@@ -630,6 +638,12 @@
             }
         }
 
+        if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) {
+            // Remote selectable codec could be changed if codec config changed
+            // in connected state, we need to re-check optional codec status
+            // for this codec change event.
+            mA2dpService.updateOptionalCodecsSupport(mDevice);
+        }
         if (mA2dpOffloadEnabled) {
             boolean update = false;
             BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -696,6 +710,16 @@
         return builder.toString();
     }
 
+    private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
+            BluetoothCodecStatus newCodecStatus) {
+        if (prevCodecStatus == null) {
+            return false;
+        }
+        return BluetoothCodecStatus.sameCapabilities(
+                prevCodecStatus.getCodecsSelectableCapabilities(),
+                newCodecStatus.getCodecsSelectableCapabilities());
+    }
+
     private static String messageWhatToString(int what) {
         switch (what) {
             case CONNECT:
diff --git a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index 99e9976..b5886c2 100644
--- a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -152,11 +152,7 @@
 
                     case MSG_TIMEOUT: {
                         Log.v(TAG, "Timed out waiting for players");
-                        for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
-                            if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
-                            wrapper.disconnect();
-                        }
-                        mPendingPlayers.clear();
+                        removePendingPlayers();
                     } break;
                 }
 
@@ -169,4 +165,21 @@
             }
         };
     }
+
+    private void removePendingPlayers() {
+        for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
+            if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
+            wrapper.disconnect();
+        }
+        mPendingPlayers.clear();
+    }
+
+    void cleanup() {
+        if (mPendingPlayers.size() != 0) {
+            Log.i(TAG, "Bluetooth turn off with " + mPendingPlayers.size() + " pending player(s)");
+            mHandler.removeMessages(MSG_TIMEOUT);
+            removePendingPlayers();
+            mHandler = null;
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 6148d4d..10d99bf 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -89,6 +89,7 @@
     private int mActivePlayerId = NO_ACTIVE_PLAYER;
 
     private AvrcpTargetService.ListCallback mCallback;
+    private BrowsablePlayerConnector mBrowsablePlayerConnector;
 
     interface MediaUpdateCallback {
         void run(MediaData data);
@@ -140,8 +141,8 @@
                     .getPackageManager()
                     .queryIntentServices(intent, PackageManager.MATCH_ALL);
 
-        BrowsablePlayerConnector.connectToPlayers(mContext, mLooper, playerList,
-                (List<BrowsedPlayerWrapper> players) -> {
+        mBrowsablePlayerConnector = BrowsablePlayerConnector.connectToPlayers(mContext, mLooper,
+                playerList, (List<BrowsedPlayerWrapper> players) -> {
                 Log.i(TAG, "init: Browsable Player list size is " + players.size());
 
                 // Check to see if the list has been cleaned up before this completed
@@ -196,6 +197,9 @@
         }
         mMediaPlayers.clear();
 
+        if (mBrowsablePlayerConnector != null) {
+            mBrowsablePlayerConnector.cleanup();
+        }
         for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
             player.disconnect();
         }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 323dbfb..4dfb8d4 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -23,7 +23,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.media.MediaMetadata;
+import android.media.MediaPlayer;
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.MediaController;
@@ -95,8 +98,16 @@
             "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
 
     private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
+
+    // In order to be considered as an audio source capable of receiving media key events (In the
+    // eyes of MediaSessionService), we need an active MediaPlayer in addition to a MediaSession.
+    // Because of this, the media player below plays an incredibly short, silent audio sample so
+    // that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the current
+    // active player and send us media events. This is a restriction currently imposed by the media
+    // framework code and could be reconsidered in the future.
     private MediaSession mSession;
     private MediaMetadata mA2dpMetadata;
+    private MediaPlayer mMediaPlayer;
 
     private AvrcpControllerService mAvrcpCtrlSrvc;
     private boolean mBrowseConnected = false;
@@ -158,23 +169,37 @@
         }
     }
 
+    /**
+     * Initialize this BluetoothMediaBrowserService, creating our MediaSession, 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);
-        setSessionToken(mSession.getSessionToken());
         mSession.setCallback(mSessionCallbacks);
         mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
                 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-        mSession.setActive(true);
         mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
         mSession.setQueue(mMediaQueue);
+
+        // Create and setup the MediaPlayer
+        initMediaPlayer();
+
+        // Associate the held MediaSession with this browser and activate it
+        setSessionToken(mSession.getSessionToken());
+        mSession.setActive(true);
+
+        // Internal handler to process events and requests
         mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
 
+        // Set the initial Media state (sets current playback state and media meta data)
         refreshInitialPlayingState();
 
+        // Set up communication with the controller service
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
@@ -185,19 +210,82 @@
         synchronized (this) {
             mParentIdToRequestMap.clear();
         }
-        setBluetoothMediaBrowserService(this);
 
+        setBluetoothMediaBrowserService(this);
     }
 
+    /**
+     * Clean up this instance in the reverse order that we created it.
+     */
     @Override
     public void onDestroy() {
         if (DBG) Log.d(TAG, "onDestroy");
         setBluetoothMediaBrowserService(null);
-        mSession.release();
         unregisterReceiver(mBtReceiver);
+        destroyMediaPlayer();
+        mSession.release();
         super.onDestroy();
     }
 
+    /**
+     * Initializes the silent MediaPlayer object which aids in receiving media key focus.
+     *
+     * The created MediaPlayer is already prepared and will release and stop itself on error. All
+     * you need to do is start() it.
+     */
+    private void initMediaPlayer() {
+        if (DBG) Log.d(TAG, "initMediaPlayer()");
+
+        // Parameters for create
+        AudioAttributes attrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
+        AudioManager am = getSystemService(AudioManager.class);
+
+        // Create our player object. Returns a prepared player on success, null on failure
+        mMediaPlayer = MediaPlayer.create(this, R.raw.silent, attrs, am.generateAudioSessionId());
+        if (mMediaPlayer == null) {
+            Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+            return;
+        }
+
+        // Set other player attributes
+        mMediaPlayer.setLooping(false);
+        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+            destroyMediaPlayer();
+            return false;
+        });
+    }
+
+    /**
+     * Safely tears down our local MediaPlayer
+     */
+    private void destroyMediaPlayer() {
+        if (DBG) Log.d(TAG, "destroyMediaPlayer()");
+        if (mMediaPlayer == null) {
+            return;
+        }
+        mMediaPlayer.stop();
+        mMediaPlayer.release();
+        mMediaPlayer = null;
+    }
+
+    /**
+     * Uses the internal MediaPlayer to play a silent, short audio sample so that AudioService will
+     * treat us as the active MediaSession/MediaPlayer combo and properly route us media key events.
+     *
+     * If the MediaPlayer failed to initialize properly, this call will fail gracefully and log the
+     * failed attempt. Media keys will not be routed.
+     */
+    private void getMediaKeyFocus() {
+        if (DBG) Log.d(TAG, "getMediaKeyFocus()");
+        if (mMediaPlayer == null) {
+            Log.w(TAG, "Media player is null. Can't get media key focus. Media keys may not route");
+            return;
+        }
+        mMediaPlayer.start();
+    }
 
     /**
      *  getBluetoothMediaBrowserService()
@@ -311,6 +399,7 @@
             if (DBG) Log.d(TAG, "onPrepare");
             if (mA2dpSinkService != null) {
                 mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+                getMediaKeyFocus();
             }
         }
 
@@ -336,6 +425,7 @@
                 // Play the item if possible.
                 if (mA2dpSinkService != null) {
                     mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+                    getMediaKeyFocus();
                 }
                 mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
             }
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 40528b2..76cf66d 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -66,8 +66,6 @@
 import android.util.SparseArray;
 import android.util.StatsLog;
 
-import androidx.room.Room;
-
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
@@ -426,9 +424,7 @@
         mActiveDeviceManager.start();
 
         mDatabaseManager = new DatabaseManager(this);
-        MetadataDatabase database = Room.databaseBuilder(this,
-                MetadataDatabase.class, MetadataDatabase.DATABASE_NAME).build();
-        mDatabaseManager.start(database);
+        mDatabaseManager.start(MetadataDatabase.createDatabase(this));
 
         mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(),
                 Looper.getMainLooper());
@@ -2712,7 +2708,7 @@
      */
     @VisibleForTesting
     public void metadataChanged(String address, int key, String value) {
-        BluetoothDevice device = mRemoteDevices.getDevice(address.getBytes());
+        BluetoothDevice device = mRemoteDevices.getDevice(Utils.getBytesFromAddress(address));
         if (mMetadataListeners.containsKey(device)) {
             ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device);
             for (IBluetoothMetadataListener listener : list) {
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 6d0c430..ee7fb58 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -455,6 +455,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
     }
 
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index 3ba439d..00fd7ba 100644
--- a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -268,8 +268,9 @@
      * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
      * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
      * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
-     * {@link BluetoothProfile#MAP}, {@link BluetoothProfile#MAP_CLIENT},
-     * {@link BluetoothProfile#SAP}, {@link BluetoothProfile#HEARING_AID}
+     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
+     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
+     * {@link BluetoothProfile#HEARING_AID}
      * @param newPriority the priority to set; one of
      * {@link BluetoothProfile#PRIORITY_UNDEFINED},
      * {@link BluetoothProfile#PRIORITY_OFF},
@@ -326,8 +327,9 @@
      * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
      * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
      * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
-     * {@link BluetoothProfile#MAP}, {@link BluetoothProfile#MAP_CLIENT},
-     * {@link BluetoothProfile#SAP}, {@link BluetoothProfile#HEARING_AID}
+     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
+     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
+     * {@link BluetoothProfile#HEARING_AID}
      * @return the profile priority of the device; one of
      * {@link BluetoothProfile#PRIORITY_UNDEFINED},
      * {@link BluetoothProfile#PRIORITY_OFF},
@@ -671,6 +673,9 @@
             int pbapPriority = Settings.Global.getInt(contentResolver,
                     Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
                     BluetoothProfile.PRIORITY_UNDEFINED);
+            int pbapClientPriority = Settings.Global.getInt(contentResolver,
+                    Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
+                    BluetoothProfile.PRIORITY_UNDEFINED);
             int sapPriority = Settings.Global.getInt(contentResolver,
                     Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
                     BluetoothProfile.PRIORITY_UNDEFINED);
@@ -690,6 +695,7 @@
             data.setProfilePriority(BluetoothProfile.HID_HOST, hidHostPriority);
             data.setProfilePriority(BluetoothProfile.PAN, panPriority);
             data.setProfilePriority(BluetoothProfile.PBAP, pbapPriority);
+            data.setProfilePriority(BluetoothProfile.PBAP_CLIENT, pbapClientPriority);
             data.setProfilePriority(BluetoothProfile.MAP, mapPriority);
             data.setProfilePriority(BluetoothProfile.MAP_CLIENT, mapClientPriority);
             data.setProfilePriority(BluetoothProfile.SAP, sapPriority);
diff --git a/src/com/android/bluetooth/btservice/storage/Metadata.java b/src/com/android/bluetooth/btservice/storage/Metadata.java
index 8678814..5700e69 100644
--- a/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -37,7 +37,7 @@
     public boolean migrated;
 
     @Embedded
-    public ProfilePrioritiesEntity profilePriorites;
+    public ProfilePrioritiesEntity profilePriorities;
 
     @Embedded
     @NonNull
@@ -49,7 +49,7 @@
     Metadata(String address) {
         this.address = address;
         migrated = false;
-        profilePriorites = new ProfilePrioritiesEntity();
+        profilePriorities = new ProfilePrioritiesEntity();
         publicMetadata = new CustomizedMetadataEntity();
         a2dpSupportsOptionalCodecs = BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
         a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
@@ -62,37 +62,40 @@
     void setProfilePriority(int profile, int priority) {
         switch (profile) {
             case BluetoothProfile.A2DP:
-                profilePriorites.a2dp_priority = priority;
+                profilePriorities.a2dp_priority = priority;
                 break;
             case BluetoothProfile.A2DP_SINK:
-                profilePriorites.a2dp_sink_priority = priority;
+                profilePriorities.a2dp_sink_priority = priority;
                 break;
             case BluetoothProfile.HEADSET:
-                profilePriorites.hfp_priority = priority;
+                profilePriorities.hfp_priority = priority;
                 break;
             case BluetoothProfile.HEADSET_CLIENT:
-                profilePriorites.hfp_client_priority = priority;
+                profilePriorities.hfp_client_priority = priority;
                 break;
             case BluetoothProfile.HID_HOST:
-                profilePriorites.hid_host_priority = priority;
+                profilePriorities.hid_host_priority = priority;
                 break;
             case BluetoothProfile.PAN:
-                profilePriorites.pan_priority = priority;
+                profilePriorities.pan_priority = priority;
                 break;
             case BluetoothProfile.PBAP:
-                profilePriorites.pbap_priority = priority;
+                profilePriorities.pbap_priority = priority;
+                break;
+            case BluetoothProfile.PBAP_CLIENT:
+                profilePriorities.pbap_client_priority = priority;
                 break;
             case BluetoothProfile.MAP:
-                profilePriorites.map_priority = priority;
+                profilePriorities.map_priority = priority;
                 break;
             case BluetoothProfile.MAP_CLIENT:
-                profilePriorites.map_client_priority = priority;
+                profilePriorities.map_client_priority = priority;
                 break;
             case BluetoothProfile.SAP:
-                profilePriorites.sap_priority = priority;
+                profilePriorities.sap_priority = priority;
                 break;
             case BluetoothProfile.HEARING_AID:
-                profilePriorites.hearing_aid_priority = priority;
+                profilePriorities.hearing_aid_priority = priority;
                 break;
             default:
                 throw new IllegalArgumentException("invalid profile " + profile);
@@ -102,27 +105,29 @@
     int getProfilePriority(int profile) {
         switch (profile) {
             case BluetoothProfile.A2DP:
-                return profilePriorites.a2dp_priority;
+                return profilePriorities.a2dp_priority;
             case BluetoothProfile.A2DP_SINK:
-                return profilePriorites.a2dp_sink_priority;
+                return profilePriorities.a2dp_sink_priority;
             case BluetoothProfile.HEADSET:
-                return profilePriorites.hfp_priority;
+                return profilePriorities.hfp_priority;
             case BluetoothProfile.HEADSET_CLIENT:
-                return profilePriorites.hfp_client_priority;
+                return profilePriorities.hfp_client_priority;
             case BluetoothProfile.HID_HOST:
-                return profilePriorites.hid_host_priority;
+                return profilePriorities.hid_host_priority;
             case BluetoothProfile.PAN:
-                return profilePriorites.pan_priority;
+                return profilePriorities.pan_priority;
             case BluetoothProfile.PBAP:
-                return profilePriorites.pbap_priority;
+                return profilePriorities.pbap_priority;
+            case BluetoothProfile.PBAP_CLIENT:
+                return profilePriorities.pbap_client_priority;
             case BluetoothProfile.MAP:
-                return profilePriorites.map_priority;
+                return profilePriorities.map_priority;
             case BluetoothProfile.MAP_CLIENT:
-                return profilePriorites.map_client_priority;
+                return profilePriorities.map_client_priority;
             case BluetoothProfile.SAP:
-                return profilePriorites.sap_priority;
+                return profilePriorities.sap_priority;
             case BluetoothProfile.HEARING_AID:
-                return profilePriorites.hearing_aid_priority;
+                return profilePriorities.hearing_aid_priority;
         }
         return BluetoothProfile.PRIORITY_UNDEFINED;
     }
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index a43d566..118dc22 100644
--- a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -16,15 +16,20 @@
 
 package com.android.bluetooth.btservice.storage;
 
+import android.content.Context;
+
 import androidx.room.Database;
+import androidx.room.Room;
 import androidx.room.RoomDatabase;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
 
 import java.util.List;
 
 /**
  * MetadataDatabase is a Room database stores Bluetooth persistence data
  */
-@Database(entities = {Metadata.class}, exportSchema = false, version = 100)
+@Database(entities = {Metadata.class}, exportSchema = false, version = 101)
 public abstract class MetadataDatabase extends RoomDatabase {
     /**
      * The database file name
@@ -34,6 +39,19 @@
     protected abstract MetadataDao mMetadataDao();
 
     /**
+     * Create a {@link MetadataDatabase} database
+     *
+     * @param context the Context to create database
+     * @return the created {@link MetadataDatabase}
+     */
+    public static MetadataDatabase createDatabase(Context context) {
+        return Room.databaseBuilder(context,
+                MetadataDatabase.class, DATABASE_NAME)
+                .addMigrations(MIGRATION_100_101)
+                .build();
+    }
+
+    /**
      * Insert a {@link Metadata} to database
      *
      * @param metadata the data wish to put into storage
@@ -66,4 +84,11 @@
     public void deleteAll() {
         mMetadataDao().deleteAll();
     }
+
+    private static final Migration MIGRATION_100_101 = new Migration(100, 101) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `pbap_client_priority` INTEGER");
+        }
+    };
 }
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
index 5537b93..f4ea719 100644
--- a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -30,6 +30,7 @@
     public int hid_host_priority;
     public int pan_priority;
     public int pbap_priority;
+    public int pbap_client_priority;
     public int map_priority;
     public int sap_priority;
     public int hearing_aid_priority;
@@ -43,6 +44,7 @@
         hid_host_priority = BluetoothProfile.PRIORITY_UNDEFINED;
         pan_priority = BluetoothProfile.PRIORITY_UNDEFINED;
         pbap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        pbap_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
         map_priority = BluetoothProfile.PRIORITY_UNDEFINED;
         sap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
         hearing_aid_priority = BluetoothProfile.PRIORITY_UNDEFINED;
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index 96022df..d54a25a 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -89,6 +89,9 @@
         /** Whether the calling app has location permission */
         boolean hasLocationPermission;
 
+        /** Whether the calling app has bluetooth privileged permission */
+        boolean hasBluetoothPrivilegedPermission;
+
         /** The user handle of the app that started the scan */
         UserHandle mUserHandle;
 
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index f3346bf..8a3a1aa 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -63,6 +63,7 @@
 import com.android.bluetooth.util.NumberUtils;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -98,6 +99,9 @@
 
     private static final int ET_LEGACY_MASK = 0x10;
 
+    private static final UUID HID_SERVICE_UUID =
+            UUID.fromString("00001812-0000-1000-8000-00805F9B34FB");
+
     private static final UUID[] HID_UUIDS = {
             UUID.fromString("00002A4A-0000-1000-8000-00805F9B34FB"),
             UUID.fromString("00002A4B-0000-1000-8000-00805F9B34FB"),
@@ -108,9 +112,8 @@
     private static final UUID ANDROID_TV_REMOTE_SERVICE_UUID =
             UUID.fromString("AB5E0001-5A21-4F05-BC7D-AF01F617B664");
 
-    private static final UUID[] FIDO_UUIDS = {
-            UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB") // U2F
-    };
+    private static final UUID FIDO_SERVICE_UUID =
+            UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB"); // U2F
 
     /**
      * Keep the arguments passed in for the PendingIntent.
@@ -160,13 +163,17 @@
     private int mMaxScanFilters;
 
     private static final int NUM_SCAN_EVENTS_KEPT = 20;
+
     /**
      * Internal list of scan events to use with the proto
      */
-    private final ArrayList<BluetoothMetricsProto.ScanEvent> mScanEvents =
-            new ArrayList<>(NUM_SCAN_EVENTS_KEPT);
+    private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents =
+            new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT);
 
-    private final Map<Integer, List<BluetoothGattService>> mGattClientDatabases = new HashMap<>();
+    /**
+     * Set of restricted (which require a BLUETOOTH_PRIVILEGED permission) handles per connectionId.
+     */
+    private final Map<Integer, Set<Integer>> mRestrictedHandles = new HashMap<>();
 
     private BluetoothAdapter mAdapter;
     private AdvertiseManager mAdvertiseManager;
@@ -277,36 +284,34 @@
         sGattService = instance;
     }
 
-    boolean permissionCheck(UUID uuid) {
-        return !(isRestrictedCharUuid(uuid) && (0 != checkCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED)));
+    private boolean permissionCheck(UUID characteristicUuid) {
+        return !isHidCharUuid(characteristicUuid)
+                || (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+                        == PERMISSION_GRANTED);
     }
 
-    boolean permissionCheck(int connId, int handle) {
-        List<BluetoothGattService> db = mGattClientDatabases.get(connId);
-        if (db == null) {
+    private boolean permissionCheck(int connId, int handle) {
+        Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+        if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
             return true;
         }
 
-        for (BluetoothGattService service : db) {
-            for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
-                if (handle == characteristic.getInstanceId()) {
-                    return !((isRestrictedCharUuid(characteristic.getUuid())
-                            || isRestrictedSrvcUuid(service.getUuid()))
-                            && (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
-                }
+        return (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+                == PERMISSION_GRANTED);
+    }
 
-                for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
-                    if (handle == descriptor.getInstanceId()) {
-                        return !((isRestrictedCharUuid(characteristic.getUuid())
-                                || isRestrictedSrvcUuid(service.getUuid())) && (0
-                                != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
-                    }
-                }
-            }
+    private boolean permissionCheck(ClientMap.App app, int connId, int handle) {
+        Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+        if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
+            return true;
         }
 
-        return true;
+        if (!app.hasBluetoothPrivilegedPermission
+                && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)== PERMISSION_GRANTED) {
+            app.hasBluetoothPrivilegedPermission = true;
+        }
+
+        return app.hasBluetoothPrivilegedPermission;
     }
 
     @Override
@@ -1309,9 +1314,13 @@
         }
 
         List<BluetoothGattService> dbOut = new ArrayList<BluetoothGattService>();
+        Set<Integer> restrictedIds = new HashSet<>();
 
         BluetoothGattService currSrvc = null;
         BluetoothGattCharacteristic currChar = null;
+        boolean isRestrictedSrvc = false;
+        boolean isHidSrvc = false;
+        boolean isRestrictedChar = false;
 
         for (GattDbElement el : db) {
             switch (el.type) {
@@ -1323,6 +1332,12 @@
 
                     currSrvc = new BluetoothGattService(el.uuid, el.id, el.type);
                     dbOut.add(currSrvc);
+                    isRestrictedSrvc =
+                            isFidoSrvcUuid(el.uuid) || isAndroidTvRemoteSrvcUuid(el.uuid);
+                    isHidSrvc = isHidSrvcUuid(el.uuid);
+                    if (isRestrictedSrvc) {
+                        restrictedIds.add(el.id);
+                    }
                     break;
 
                 case GattDbElement.TYPE_CHARACTERISTIC:
@@ -1332,6 +1347,10 @@
 
                     currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0);
                     currSrvc.addCharacteristic(currChar);
+                    isRestrictedChar = isRestrictedSrvc || (isHidSrvc && isHidCharUuid(el.uuid));
+                    if (isRestrictedChar) {
+                        restrictedIds.add(el.id);
+                    }
                     break;
 
                 case GattDbElement.TYPE_DESCRIPTOR:
@@ -1340,6 +1359,9 @@
                     }
 
                     currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
+                    if (isRestrictedChar) {
+                        restrictedIds.add(el.id);
+                    }
                     break;
 
                 case GattDbElement.TYPE_INCLUDED_SERVICE:
@@ -1358,8 +1380,10 @@
             }
         }
 
+        if (!restrictedIds.isEmpty()) {
+            mRestrictedHandles.put(connId, restrictedIds);
+        }
         // Search is complete when there was error, or nothing more to process
-        mGattClientDatabases.put(connId, dbOut);
         app.callback.onSearchComplete(address, dbOut, 0 /* status */);
     }
 
@@ -1380,13 +1404,12 @@
                     + data.length);
         }
 
-        if (!permissionCheck(connId, handle)) {
-            Log.w(TAG, "onNotify() - permission check failed!");
-            return;
-        }
-
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app != null) {
+            if (!permissionCheck(app, connId, handle)) {
+                Log.w(TAG, "onNotify() - permission check failed!");
+                return;
+            }
             app.callback.onNotify(address, handle, data);
         }
     }
@@ -2995,15 +3018,11 @@
      * Private functions
      *************************************************************************/
 
-    private boolean isRestrictedCharUuid(final UUID charUuid) {
-        return isHidUuid(charUuid);
+    private boolean isHidSrvcUuid(final UUID uuid) {
+        return HID_SERVICE_UUID.equals(uuid);
     }
 
-    private boolean isRestrictedSrvcUuid(final UUID srvcUuid) {
-        return isFidoUUID(srvcUuid) || isAndroidTvRemoteSrvcUUID(srvcUuid);
-    }
-
-    private boolean isHidUuid(final UUID uuid) {
+    private boolean isHidCharUuid(final UUID uuid) {
         for (UUID hidUuid : HID_UUIDS) {
             if (hidUuid.equals(uuid)) {
                 return true;
@@ -3012,17 +3031,12 @@
         return false;
     }
 
-    private boolean isAndroidTvRemoteSrvcUUID(final UUID uuid) {
+    private boolean isAndroidTvRemoteSrvcUuid(final UUID uuid) {
         return ANDROID_TV_REMOTE_SERVICE_UUID.equals(uuid);
     }
 
-    private boolean isFidoUUID(final UUID uuid) {
-        for (UUID fidoUuid : FIDO_UUIDS) {
-            if (fidoUuid.equals(uuid)) {
-                return true;
-            }
-        }
-        return false;
+    private boolean isFidoSrvcUuid(final UUID uuid) {
+        return FIDO_SERVICE_UUID.equals(uuid);
     }
 
     private int getDeviceType(BluetoothDevice device) {
@@ -3174,7 +3188,7 @@
     void addScanEvent(BluetoothMetricsProto.ScanEvent event) {
         synchronized (mScanEvents) {
             if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) {
-                mScanEvents.remove(0);
+                mScanEvents.remove();
             }
             mScanEvents.add(event);
         }
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 39ca3ce..541bcf4 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -658,6 +658,10 @@
                 == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
         }
+        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+                == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+        }
         if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
                 == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
@@ -1782,6 +1786,10 @@
                     == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
             }
+            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+                    == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+            }
             if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
                     == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
diff --git a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
index 91652a6..8ab9a1a 100644
--- a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
@@ -90,7 +90,8 @@
                 values.put(CallLog.Calls.TYPE, type);
                 values.put(Calls.PHONE_ACCOUNT_ID, mAccount.name);
                 List<PhoneData> phones = vcard.getPhoneList();
-                if (phones == null || phones.get(0).getNumber().equals(";")) {
+                if (phones == null || phones.get(0).getNumber().equals(";")
+                        || phones.get(0).getNumber().length() == 0) {
                     values.put(CallLog.Calls.NUMBER, "");
                 } else {
                     String phoneNumber = phones.get(0).getNumber();
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
new file mode 100644
index 0000000..4e6c351
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 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.a2dp;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class A2dpCodecConfigTest {
+    private Context mTargetContext;
+    private A2dpCodecConfig mA2dpCodecConfig;
+    private BluetoothAdapter mAdapter;
+    private BluetoothCodecConfig mCodecConfigSbc;
+    private BluetoothCodecConfig mCodecConfigAac;
+    private BluetoothCodecConfig mCodecConfigAptx;
+    private BluetoothCodecConfig mCodecConfigAptxHd;
+    private BluetoothCodecConfig mCodecConfigLdac;
+    private BluetoothDevice mTestDevice;
+
+    @Mock private A2dpNativeInterface mA2dpNativeInterface;
+
+    static final int SBC_PRIORITY_DEFAULT = 1001;
+    static final int AAC_PRIORITY_DEFAULT = 2001;
+    static final int APTX_PRIORITY_DEFAULT = 3001;
+    static final int APTX_HD_PRIORITY_DEFAULT = 4001;
+    static final int LDAC_PRIORITY_DEFAULT = 5001;
+    static final int PRIORITY_HIGH = 1000000;
+
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+
+        mA2dpCodecConfig = new A2dpCodecConfig(mTargetContext, mA2dpNativeInterface);
+        mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+
+        doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
+                any(BluetoothDevice.class),
+                any(BluetoothCodecConfig[].class));
+
+        // Create sample codec configs
+        mCodecConfigSbc = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            SBC_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+        mCodecConfigAac = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            AAC_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+        mCodecConfigAptx = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            APTX_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+        mCodecConfigAptxHd = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            APTX_HD_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+        mCodecConfigLdac = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            LDAC_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    public void testAssignCodecConfigPriorities() {
+        BluetoothCodecConfig[] codecConfigs = mA2dpCodecConfig.codecConfigPriorities();
+        for (BluetoothCodecConfig config : codecConfigs) {
+            switch(config.getCodecType()) {
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+                    Assert.assertEquals(config.getCodecPriority(), SBC_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+                    Assert.assertEquals(config.getCodecPriority(), AAC_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+                    Assert.assertEquals(config.getCodecPriority(), APTX_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+                    Assert.assertEquals(config.getCodecPriority(), APTX_HD_PRIORITY_DEFAULT);
+                    break;
+                case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+                    Assert.assertEquals(config.getCodecPriority(), LDAC_PRIORITY_DEFAULT);
+                    break;
+            }
+        }
+    }
+
+    @Test
+    public void testSetCodecPreference_priorityHighToDefault() {
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, SBC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, AAC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, APTX_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, APTX_HD_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_priorityDefaultToHigh() {
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_priorityHighToHigh() {
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                false);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+        testSetCodecPreference_codecPriorityChangeCase(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                true);
+    }
+
+    @Test
+    public void testSetCodecPreference_parametersChange() {
+        int unSupportedParameter = 200;
+
+        testSetCodecPreference_parametersChangeCase(
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                false);
+        testSetCodecPreference_parametersChangeCase(
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                true);
+        testSetCodecPreference_parametersChangeCase(
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                unSupportedParameter,
+                false);
+
+        testSetCodecPreference_parametersChangeCase(
+                BluetoothCodecConfig.SAMPLE_RATE_48000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                true);
+        testSetCodecPreference_parametersChangeCase(
+                BluetoothCodecConfig.SAMPLE_RATE_48000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                true);
+        testSetCodecPreference_parametersChangeCase(
+                BluetoothCodecConfig.SAMPLE_RATE_48000,
+                unSupportedParameter,
+                false);
+
+        testSetCodecPreference_parametersChangeCase(
+                unSupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                false);
+        testSetCodecPreference_parametersChangeCase(
+                unSupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                false);
+        testSetCodecPreference_parametersChangeCase(
+                unSupportedParameter,
+                unSupportedParameter,
+                false);
+    }
+
+    @Test
+    public void testDisableOptionalCodecs() {
+        int verifyCount = 0;
+        BluetoothCodecConfig[] codecConfigArray =
+                new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+        codecConfigArray[0] = new BluetoothCodecConfig(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                0, 0, 0, 0);       // Codec-specific fields
+
+        // Test don't invoke to native when current codec is SBC
+        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigSbc);
+        verify(mA2dpNativeInterface, times(verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+        // Test invoke to native when current codec is not SBC
+        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAac);
+        verify(mA2dpNativeInterface, times(++verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptx);
+        verify(mA2dpNativeInterface, times(++verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
+        verify(mA2dpNativeInterface, times(++verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigLdac);
+        verify(mA2dpNativeInterface, times(++verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+    }
+
+    @Test
+    public void testEnableOptionalCodecs() {
+        int verifyCount = 0;
+        BluetoothCodecConfig[] codecConfigArray =
+                new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+        codecConfigArray[0] = new BluetoothCodecConfig(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                SBC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                0, 0, 0, 0);       // Codec-specific fields
+
+        // Test invoke to native when current codec is SBC
+        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigSbc);
+        verify(mA2dpNativeInterface, times(++verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+        // Test don't invoke to native when current codec is not SBC
+        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAac);
+        verify(mA2dpNativeInterface, times(verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptx);
+        verify(mA2dpNativeInterface, times(verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
+        verify(mA2dpNativeInterface, times(verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigLdac);
+        verify(mA2dpNativeInterface, times(verifyCount))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+    }
+
+    private void testSetCodecPreference_parametersChangeCase(int sampleRate, int bitPerSample,
+            boolean invokeNative) {
+        BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[1];
+        invalidSelectableCodecs[0] = new BluetoothCodecConfig(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                LDAC_PRIORITY_DEFAULT,
+                (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
+                (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                0, 0, 0, 0);       // Codec-specific fields
+
+
+        BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[2];
+        selectableCodecs[0] = mCodecConfigSbc;
+        selectableCodecs[1] = new BluetoothCodecConfig(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                LDAC_PRIORITY_DEFAULT,
+                (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
+                (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                0, 0, 0, 0);       // Codec-specific fields
+
+        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
+        codecConfigArray[0] = new BluetoothCodecConfig(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                LDAC_PRIORITY_DEFAULT,
+                sampleRate,
+                bitPerSample,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                0, 0, 0, 0);       // Codec-specific fields
+
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
+                                                                    invalidSelectableCodecs,
+                                                                    invalidSelectableCodecs);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+
+        codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
+                                               selectableCodecs,
+                                               selectableCodecs);
+
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+        verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+    }
+
+    private void testSetCodecPreference_codecPriorityChangeCase(int newCodecType,
+            int newCodecPriority, int oldCodecType, int oldCodecPriority, boolean invokeNative) {
+        BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[5];
+        selectableCodecs[0] = mCodecConfigSbc;
+        selectableCodecs[1] = mCodecConfigAac;
+        selectableCodecs[2] = mCodecConfigAptx;
+        selectableCodecs[3] = mCodecConfigAptxHd;
+        selectableCodecs[4] = mCodecConfigLdac;
+
+        BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[4];
+        invalidSelectableCodecs[0] = mCodecConfigAac;
+        invalidSelectableCodecs[1] = mCodecConfigAptx;
+        invalidSelectableCodecs[2] = mCodecConfigAptxHd;
+        invalidSelectableCodecs[3] = mCodecConfigLdac;
+
+        BluetoothCodecConfig oldCodecConfig =  new BluetoothCodecConfig(
+                oldCodecType,
+                oldCodecPriority,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                0, 0, 0, 0);       // Codec-specific fields
+
+        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
+        codecConfigArray[0] = new BluetoothCodecConfig(
+            newCodecType,
+            newCodecPriority,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                    invalidSelectableCodecs,
+                                                                    invalidSelectableCodecs);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+
+        codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                               selectableCodecs,
+                                               selectableCodecs);
+
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+        verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index af872d1..9c01854 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -1073,7 +1073,6 @@
                         BluetoothCodecConfig.BITS_PER_SAMPLE_16,
                         BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                         0, 0, 0, 0);       // Codec-specific fields
-        BluetoothCodecConfig codecConfig = codecConfigSbc;
 
         BluetoothCodecConfig[] codecsLocalCapabilities;
         BluetoothCodecConfig[] codecsSelectableCapabilities;
@@ -1090,24 +1089,36 @@
             codecsLocalCapabilities[0] = codecConfigSbc;
             codecsSelectableCapabilities[0] = codecConfigSbc;
         }
-        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfig,
-                                                                    codecsLocalCapabilities,
-                                                                    codecsSelectableCapabilities);
+        BluetoothCodecConfig[] badCodecsSelectableCapabilities;
+        badCodecsSelectableCapabilities = new BluetoothCodecConfig[1];
+        badCodecsSelectableCapabilities[0] = codecConfigAac;
+
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfigSbc,
+                codecsLocalCapabilities, codecsSelectableCapabilities);
+        BluetoothCodecStatus badCodecStatus = new BluetoothCodecStatus(codecConfigAac,
+                codecsLocalCapabilities, badCodecsSelectableCapabilities);
 
         when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice))
                 .thenReturn(previousSupport);
         when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice))
                 .thenReturn(previousEnabled);
-        connectDeviceWithCodecStatus(mTestDevice, codecStatus);
 
+        // Generate connection request from native with bad codec status
+        connectDeviceWithCodecStatus(mTestDevice, badCodecStatus);
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+
+        // Generate connection request from native with good codec status
+        connectDeviceWithCodecStatus(mTestDevice, codecStatus);
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+
+        // Check optional codec status is set properly
         verify(mDatabaseManager, times(verifyNotSupportTime)).setA2dpSupportsOptionalCodecs(
                 mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
         verify(mDatabaseManager, times(verifySupportTime)).setA2dpSupportsOptionalCodecs(
                 mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
         verify(mDatabaseManager, times(verifyEnabledTime)).setA2dpOptionalCodecsEnabled(
                 mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
-
-        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                            BluetoothProfile.STATE_CONNECTED);
     }
 }
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
index 0b825bf..b8b69c0 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -20,6 +20,8 @@
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
@@ -54,6 +56,9 @@
     private BluetoothDevice mTestDevice;
     private static final int TIMEOUT_MS = 1000;    // 1s
 
+    private BluetoothCodecConfig mCodecConfigSbc;
+    private BluetoothCodecConfig mCodecConfigAac;
+
     @Mock private AdapterService mAdapterService;
     @Mock private A2dpService mA2dpService;
     @Mock private A2dpNativeInterface mA2dpNativeInterface;
@@ -72,6 +77,22 @@
         // Get a device for testing
         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
 
+        // Set up sample codec config
+        mCodecConfigSbc = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_44100,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+        mCodecConfigAac = new BluetoothCodecConfig(
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+            BluetoothCodecConfig.SAMPLE_RATE_48000,
+            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+            0, 0, 0, 0);       // Codec-specific fields
+
         // Set up thread and looper
         mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
         mHandlerThread.start();
@@ -255,4 +276,87 @@
         Assert.assertThat(mA2dpStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
     }
+
+    /**
+     * Test that codec config change been reported to A2dpService properly.
+     */
+    @Test
+    public void testProcessCodecConfigEvent() {
+        testProcessCodecConfigEventCase(false);
+    }
+
+    /**
+     * Test that codec config change been reported to A2dpService properly when
+     * A2DP hardware offloading is enabled.
+     */
+    @Test
+    public void testProcessCodecConfigEvent_OffloadEnabled() {
+        testProcessCodecConfigEventCase(true);
+    }
+
+    /**
+     * Helper methold to test processCodecConfigEvent()
+     */
+    public void testProcessCodecConfigEventCase(boolean offloadEnabled) {
+        if (offloadEnabled) {
+            mA2dpStateMachine.mA2dpOffloadEnabled = true;
+        }
+
+        doNothing().when(mA2dpService).codecConfigUpdated(any(BluetoothDevice.class),
+                any(BluetoothCodecStatus.class), anyBoolean());
+        doNothing().when(mA2dpService).updateOptionalCodecsSupport(any(BluetoothDevice.class));
+        allowConnection(true);
+
+        BluetoothCodecConfig[] codecsSelectableSbc;
+        codecsSelectableSbc = new BluetoothCodecConfig[1];
+        codecsSelectableSbc[0] = mCodecConfigSbc;
+
+        BluetoothCodecConfig[] codecsSelectableSbcAac;
+        codecsSelectableSbcAac = new BluetoothCodecConfig[2];
+        codecsSelectableSbcAac[0] = mCodecConfigSbc;
+        codecsSelectableSbcAac[1] = mCodecConfigAac;
+
+        BluetoothCodecStatus codecStatusSbcAndSbc = new BluetoothCodecStatus(mCodecConfigSbc,
+                codecsSelectableSbcAac, codecsSelectableSbc);
+        BluetoothCodecStatus codecStatusSbcAndSbcAac = new BluetoothCodecStatus(mCodecConfigSbc,
+                codecsSelectableSbcAac, codecsSelectableSbcAac);
+        BluetoothCodecStatus codecStatusAacAndSbcAac = new BluetoothCodecStatus(mCodecConfigAac,
+                codecsSelectableSbcAac, codecsSelectableSbcAac);
+
+        // Set default codec status when device disconnected
+        // Selected codec = SBC, selectable codec = SBC
+        mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbc);
+        verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbc, false);
+
+        // Inject an event to change state machine to connected state
+        A2dpStackEvent connStCh =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+        mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that the expected number of broadcasts are executed:
+        // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
+        // - one call to broadcastAudioState() when entering Connected state
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(intentArgument2.capture(),
+                anyString());
+
+        // Verify that state machine update optional codec when enter connected state
+        verify(mA2dpService, times(1)).updateOptionalCodecsSupport(mTestDevice);
+
+        // Change codec status when device connected.
+        // Selected codec = SBC, selectable codec = SBC+AAC
+        mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbcAac);
+        if (!offloadEnabled) {
+            verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbcAac, true);
+        }
+        verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+
+        // Update selected codec with selectable codec unchanged.
+        // Selected codec = AAC, selectable codec = SBC+AAC
+        mA2dpStateMachine.processCodecConfigEvent(codecStatusAacAndSbcAac);
+        verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusAacAndSbcAac, false);
+        verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 21a1228..651971c 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -440,7 +440,8 @@
         Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
         Assert.assertEquals(batteryLevel,
                 intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15));
-        Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, intent.getFlags());
+        Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, intent.getFlags());
     }
 
     private static Intent getHeadsetConnectionStateChangedIntent(BluetoothDevice device,