Snap for 6383292 from 985d2672738ae572afe980e3d1296db8a8071cde to q-keystone-qcom-release

Change-Id: I3cae1314d2848c379e40108598c1e8e15168c5c3
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 3fac7e3..5c9597f 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -700,7 +700,7 @@
 }
 
 static bool initNative(JNIEnv* env, jobject obj, jboolean isGuest,
-                       jboolean isSingleUserMode) {
+                       jboolean isNiapMode) {
   ALOGV("%s", __func__);
 
   android_bluetooth_UidTraffic.clazz =
@@ -716,7 +716,7 @@
 
   int ret = sBluetoothInterface->init(&sBluetoothCallbacks,
                                       isGuest == JNI_TRUE ? 1 : 0,
-                                      isSingleUserMode == JNI_TRUE ? 1 : 0);
+                                      isNiapMode == JNI_TRUE ? 1 : 0);
   if (ret != BT_STATUS_SUCCESS && ret != BT_STATUS_DONE) {
     ALOGE("Error while setting the callbacks: %d\n", ret);
     sBluetoothInterface = NULL;
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 2c6ec73..e24b808 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accediu al gestor de baixades."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permet que l\'aplicació accedeixi al gestor d\'ús compartit de Bluetooth i que l\'utilitzi per transferir fitxers."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Accés al dispositiu Bluetooth en llista blanca."</string>
+    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Accés de dispositius Bluetooth en llista blanca."</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permet que l\'aplicació col·loqui temporalment en una llista blanca un dispositiu Bluetooth, cosa que permet que el dispositiu enviï fitxers a aquest dispositiu sense la confirmació de l\'usuari."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositiu desconegut"</string>
@@ -30,7 +30,7 @@
     <string name="bt_enable_line2" msgid="4341936569415937994">"Vols activar el Bluetooth ara?"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"Cancel·la"</string>
     <string name="bt_enable_ok" msgid="3432462749994538265">"Activa"</string>
-    <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transferència del fitxer"</string>
+    <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transferència de fitxers"</string>
     <string name="incoming_file_confirm_content" msgid="2752605552743148036">"Acceptes el fitxer entrant?"</string>
     <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Rebutja"</string>
     <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepta"</string>
diff --git a/res/values-hi/test_strings.xml b/res/values-hi/test_strings.xml
index f8379b8..52e0e1a 100644
--- a/res/values-hi/test_strings.xml
+++ b/res/values-hi/test_strings.xml
@@ -3,7 +3,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="6006644116867509664">"ब्लूटूथ"</string>
     <string name="insert_record" msgid="1450997173838378132">"रिकॉर्ड सम्मिलित करें"</string>
-    <string name="update_record" msgid="2480425402384910635">"रिकॉर्ड की दुबारा पूछें"</string>
+    <string name="update_record" msgid="2480425402384910635">"रिकॉर्ड की पुष्टि करें"</string>
     <string name="ack_record" msgid="6716152390978472184">"रिकॉर्ड अभिस्वीकृत करें"</string>
     <string name="deleteAll_record" msgid="4383349788485210582">"सभी रिकॉर्ड मिटाएं"</string>
     <string name="ok_button" msgid="6519033415223065454">"ठीक है"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7fe6a4f..415bd10 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -69,7 +69,7 @@
     <string name="upload_succ_ok" msgid="7705428476405478828">"אישור"</string>
     <string name="upload_fail_line1" msgid="7899394672421491701">"הקובץ לא נשלח אל <xliff:g id="RECIPIENT">%1$s</xliff:g>."</string>
     <string name="upload_fail_line1_2" msgid="2108129204050841798">"קובץ: <xliff:g id="FILE">%1$s</xliff:g>"</string>
-    <string name="upload_fail_ok" msgid="5807702461606714296">"נסה שוב"</string>
+    <string name="upload_fail_ok" msgid="5807702461606714296">"כדאי לנסות שוב"</string>
     <string name="upload_fail_cancel" msgid="9118496285835687125">"סגור"</string>
     <string name="bt_error_btn_ok" msgid="5965151173011534240">"אישור"</string>
     <string name="unknown_file" msgid="6092727753965095366">"קובץ לא ידוע"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a7c0f1e..4db5d0a 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -127,7 +127,7 @@
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"रद्द करा"</string>
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"तुम्ही ब्लूटूथद्वारे शेअर करू इच्छित असलेली खाती निवडा. कनेक्ट करताना अद्याप तुम्ही खात्यांमधील कोणताही अ‍ॅक्सेस स्वीकारण्याची आवश्यकता आहे."</string>
     <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"स्लॉट शिल्लक:"</string>
-    <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"अॅप्लिकेशन आयकन"</string>
+    <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ॲप्लिकेशन आयकन"</string>
     <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ब्लूटूथ मेसेज सामायिकरण सेटिंग्ज"</string>
     <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"खाते निवडू शकत नाही. 0 स्लॉट शिल्लक"</string>
     <string name="bluetooth_connected" msgid="6718623220072656906">"ब्लूटूथ ऑडिओ कनेक्ट केला"</string>
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 2d2f34a..81ba906 100755
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -1225,10 +1225,12 @@
             Log.e(TAG, "setCodecConfigPreference: Invalid device");
             return;
         }
-
+        if (codecConfig == null) {
+            Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
+            return;
+        }
         long cs4 = codecConfig.getCodecSpecific4();
-            GattService mGattService = GattService.getGattService();
-
+        GattService mGattService = GattService.getGattService();
         if(cs4 > 0 && mGattService != null) {
             switch((int)(cs4 & APTX_MODE_MASK)) {
                 case APTX_HQ:
@@ -1248,10 +1250,6 @@
             }
         }
 
-        if (codecConfig == null) {
-            Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
-            return;
-        }
         BluetoothCodecStatus codecStatus = getCodecStatus(device);
         if (codecStatus == null) {
             Log.e(TAG, "setCodecConfigPreference: Codec status is null");
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 7ea611a..13e73f2 100755
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -15,8 +15,10 @@
  */
 package com.android.bluetooth.a2dpsink;
 
-import android.bluetooth.BluetoothAdapter;
+import static android.bluetooth.BluetoothProfile.PRIORITY_OFF;
+
 import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -179,6 +181,15 @@
             switch (event.mType) {
                 case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
                     switch (event.mState) {
+                        case StackEvent.CONNECTION_STATE_CONNECTING:
+                            if (mService.getPriority(mDevice) == PRIORITY_OFF) {
+                                Log.w(TAG, "Ignore incoming connection, profile is"
+                                        + " turned off for " + mDevice);
+                                mService.disconnectA2dpNative(mDeviceAddress);
+                            } else {
+                                transitionTo(mConnecting);
+                            }
+                            break;
                         case StackEvent.CONNECTION_STATE_CONNECTED:
                             transitionTo(mConnected);
                             break;
diff --git a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index b5886c2..3ce2c5d 100644
--- a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -23,6 +23,9 @@
 import android.os.Message;
 import android.util.Log;
 
+import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -47,6 +50,7 @@
     private static final int MSG_CONNECT_CB = 1;
     private static final int MSG_TIMEOUT = 2;
 
+    private static BrowsablePlayerConnector sInjectConnector;
     private Handler mHandler;
     private Context mContext;
     private PlayerListCallback mCallback;
@@ -58,11 +62,19 @@
         void run(List<BrowsedPlayerWrapper> result);
     }
 
+    private static void setInstanceForTesting(BrowsablePlayerConnector connector) {
+        Utils.enforceInstrumentationTestMode();
+        sInjectConnector = connector;
+    }
+
     static BrowsablePlayerConnector connectToPlayers(
             Context context,
             Looper looper,
             List<ResolveInfo> players,
             PlayerListCallback cb) {
+        if (sInjectConnector != null) {
+            return sInjectConnector;
+        }
         if (cb == null) {
             Log.wtfStack(TAG, "Null callback passed");
             return null;
diff --git a/src/com/android/bluetooth/avrcp/GPMWrapper.java b/src/com/android/bluetooth/avrcp/GPMWrapper.java
index 87e5ec0..ea9875d 100644
--- a/src/com/android/bluetooth/avrcp/GPMWrapper.java
+++ b/src/com/android/bluetooth/avrcp/GPMWrapper.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.avrcp;
 
 import android.media.session.MediaSession;
+import android.os.Looper;
 import android.util.Log;
 
 /**
@@ -28,6 +29,10 @@
     private static final String TAG = "AvrcpGPMWrapper";
     private static final boolean DEBUG = true;
 
+    GPMWrapper(MediaController controller, Looper looper) {
+        super(controller, looper);
+    }
+
     @Override
     boolean isMetadataSynced() {
         if (getQueue() == null) {
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 29f4cf9..5756121 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -24,6 +24,9 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
@@ -33,6 +36,7 @@
 import android.view.KeyEvent;
 
 import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -79,6 +83,8 @@
     private Looper mLooper; // Thread all media player callbacks and timeouts happen on
     private PackageManager mPackageManager;
     private MediaSessionManager mMediaSessionManager;
+    private MediaData mCurrMediaData = null;
+    private final AudioManager mAudioManager;
 
     private Map<Integer, MediaPlayerWrapper> mMediaPlayers =
             Collections.synchronizedMap(new HashMap<Integer, MediaPlayerWrapper>());
@@ -88,6 +94,9 @@
             Collections.synchronizedMap(new HashMap<Integer, BrowsedPlayerWrapper>());
     private int mActivePlayerId = NO_ACTIVE_PLAYER;
 
+    @VisibleForTesting
+    private boolean mAudioPlaybackIsActive = false;
+
     private AvrcpTargetService.ListCallback mCallback;
     private BrowsablePlayerConnector mBrowsablePlayerConnector;
 
@@ -122,6 +131,9 @@
         pkgFilter.addDataScheme(PACKAGE_SCHEME);
         context.registerReceiver(mPackageChangedBroadcastReceiver, pkgFilter);
 
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mAudioManager.registerAudioPlaybackCallback(mAudioPlaybackCallback, new Handler(mLooper));
+
         mMediaSessionManager =
                 (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
         mMediaSessionManager.addOnActiveSessionsChangedListener(
@@ -190,6 +202,8 @@
         mMediaSessionManager.setCallback(null, null);
         mMediaSessionManager = null;
 
+        mAudioManager.unregisterAudioPlaybackCallback(mAudioPlaybackCallback);
+
         mMediaPlayerIds.clear();
 
         for (MediaPlayerWrapper player : mMediaPlayers.values()) {
@@ -275,7 +289,16 @@
         final MediaPlayerWrapper player = getActivePlayer();
         if (player == null) return null;
 
-        return player.getPlaybackState();
+        PlaybackState state = player.getPlaybackState();
+        if (mAudioPlaybackIsActive
+                && (state == null || state.getState() != PlaybackState.STATE_PLAYING)) {
+            return new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING,
+                          state == null ? 0 : state.getPosition(),
+                          1.0f)
+                .build();
+        }
+        return state;
     }
 
     @NonNull
@@ -406,12 +429,8 @@
         }
     }
 
-    // Adds the controller to the MediaPlayerList or updates the controller if we already had
-    // a controller for a package. Returns the new ID of the controller where its added or its
-    // previous value if it already existed. Returns -1 if the controller passed in is invalid
-    int addMediaPlayer(android.media.session.MediaController controller) {
-        if (controller == null) return -1;
-
+    @VisibleForTesting
+    int addMediaPlayer(MediaController controller) {
         // Each new player has an ID of 1 plus the highest ID. The ID 0 is reserved to signify that
         // there is no active player. If we already have a browsable player for the package, reuse
         // that key.
@@ -427,7 +446,7 @@
         if (mMediaPlayers.containsKey(playerId)) {
             d("Already have a controller for the player: " + packageName + ", updating instead");
             MediaPlayerWrapper player = mMediaPlayers.get(playerId);
-            player.updateMediaController(MediaControllerFactory.wrap(controller));
+            player.updateMediaController(controller);
 
             // If the media controller we updated was the active player check if the media updated
             if (playerId == mActivePlayerId) {
@@ -437,8 +456,8 @@
             return playerId;
         }
 
-        MediaPlayerWrapper newPlayer = MediaPlayerWrapper.wrap(
-                MediaControllerFactory.wrap(controller),
+        MediaPlayerWrapper newPlayer = MediaPlayerWrapperFactory.wrap(
+                controller,
                 mLooper);
 
         Log.i(TAG, "Adding wrapped media player: " + packageName + " at key: "
@@ -448,6 +467,18 @@
         return playerId;
     }
 
+    // Adds the controller to the MediaPlayerList or updates the controller if we already had
+    // a controller for a package. Returns the new ID of the controller where its added or its
+    // previous value if it already existed. Returns -1 if the controller passed in is invalid
+    int addMediaPlayer(android.media.session.MediaController controller) {
+        if (controller == null) {
+            e("Trying to add a null MediaController");
+            return -1;
+        }
+
+        return addMediaPlayer(MediaControllerFactory.wrap(controller));
+    }
+
     void removeMediaPlayer(int playerId) {
         if (!mMediaPlayers.containsKey(playerId)) {
             e("Trying to remove nonexistent media player: " + playerId);
@@ -495,7 +526,12 @@
             sendFolderUpdate(true, true, false);
         }
 
-        sendMediaUpdate(getActivePlayer().getCurrentMediaData());
+        MediaData data = getActivePlayer().getCurrentMediaData();
+        if (mAudioPlaybackIsActive) {
+            data.state = mCurrMediaData.state;
+            Log.d(TAG, "setActivePlayer mAudioPlaybackIsActive=true, state=" + data.state);
+        }
+        sendMediaUpdate(data);
     }
 
     // TODO (apanicke): Add logging for media key events in dumpsys
@@ -528,6 +564,8 @@
             data.queue.add(data.metadata);
         }
 
+        Log.d(TAG, "sendMediaUpdate state=" + data.state);
+        mCurrMediaData = data;
         mCallback.run(data);
     }
 
@@ -591,6 +629,78 @@
         }
     };
 
+    void updateMediaForAudioPlayback() {
+        MediaData currMediaData = null;
+        PlaybackState currState = null;
+        if (getActivePlayer() == null) {
+            Log.d(TAG, "updateMediaForAudioPlayback: no active player");
+            PlaybackState.Builder builder = new PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_STOPPED, 0L, 0L);
+            List<Metadata> queue = new ArrayList<Metadata>();
+            queue.add(Util.empty_data());
+            currMediaData = new MediaData(
+                    Util.empty_data(),
+                    builder.build(),
+                    queue
+                );
+        } else {
+            currMediaData = getActivePlayer().getCurrentMediaData();
+            currState = currMediaData.state;
+        }
+
+        if (currState != null
+                && currState.getState() == PlaybackState.STATE_PLAYING) {
+            Log.i(TAG, "updateMediaForAudioPlayback: Active player is playing, drop it");
+            return;
+        }
+
+        if (mAudioPlaybackIsActive) {
+            PlaybackState.Builder builder = new PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_PLAYING,
+                        currState == null ? 0 : currState.getPosition(),
+                        1.0f);
+            currMediaData.state = builder.build();
+        }
+        Log.i(TAG, "updateMediaForAudioPlayback: update state=" + currMediaData.state);
+        sendMediaUpdate(currMediaData);
+    }
+
+    @VisibleForTesting
+    void injectAudioPlaybacActive(boolean isActive) {
+        mAudioPlaybackIsActive = isActive;
+        updateMediaForAudioPlayback();
+    }
+
+    private final AudioManager.AudioPlaybackCallback mAudioPlaybackCallback =
+            new AudioManager.AudioPlaybackCallback() {
+        @Override
+        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+            if (configs == null) {
+                return;
+            }
+            boolean isActive = false;
+            Log.v(TAG, "onPlaybackConfigChanged(): Configs list size=" + configs.size());
+            for (AudioPlaybackConfiguration config : configs) {
+                if (config.isActive() && (config.getAudioAttributes().getUsage()
+                            == AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+                        && (config.getAudioAttributes().getContentType()
+                            == AudioAttributes.CONTENT_TYPE_SPEECH)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onPlaybackConfigChanged(): config="
+                                 + AudioPlaybackConfiguration.toLogFriendlyString(config));
+                    }
+                    isActive = true;
+                }
+            }
+            if (isActive != mAudioPlaybackIsActive) {
+                Log.d(TAG, "onPlaybackConfigChanged isActive=" + isActive
+                        + ", mAudioPlaybackIsActive=" + mAudioPlaybackIsActive);
+                mAudioPlaybackIsActive = isActive;
+                updateMediaForAudioPlayback();
+            }
+        }
+    };
+
     private final MediaPlayerWrapper.Callback mMediaPlayerCallback =
             new MediaPlayerWrapper.Callback() {
         @Override
@@ -605,6 +715,10 @@
                 return;
             }
 
+            if (mAudioPlaybackIsActive && (data.state.getState() != PlaybackState.STATE_PLAYING)) {
+                Log.d(TAG, "Some audio playbacks are still active, drop it");
+                return;
+            }
             sendMediaUpdate(data);
         }
     };
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
index e4176b3..9eba036 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
@@ -54,10 +54,6 @@
     private final Object mCallbackLock = new Object();
     private Callback mRegisteredCallback = null;
 
-    protected MediaPlayerWrapper() {
-        mCurrentData = new MediaData(null, null, null);
-    }
-
     public interface Callback {
         void mediaUpdatedCallback(MediaData data);
     }
@@ -80,30 +76,15 @@
         return true;
     }
 
-    // TODO (apanicke): Implement a factory to make testing and creating interop wrappers easier
-    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
-        if (controller == null || looper == null) {
-            e("MediaPlayerWrapper.wrap(): Null parameter - Controller: " + controller
-                    + " | Looper: " + looper);
-            return null;
-        }
+    MediaPlayerWrapper(MediaController controller, Looper looper) {
+        mMediaController = controller;
+        mPackageName = controller.getPackageName();
+        mLooper = looper;
 
-        MediaPlayerWrapper newWrapper;
-        if (controller.getPackageName().equals("com.google.android.music")) {
-            Log.v(TAG, "Creating compatibility wrapper for Google Play Music");
-            newWrapper = new GPMWrapper();
-        } else {
-            newWrapper = new MediaPlayerWrapper();
-        }
-
-        newWrapper.mMediaController = controller;
-        newWrapper.mPackageName = controller.getPackageName();
-        newWrapper.mLooper = looper;
-
-        newWrapper.mCurrentData.queue = Util.toMetadataList(newWrapper.getQueue());
-        newWrapper.mCurrentData.metadata = Util.toMetadata(newWrapper.getMetadata());
-        newWrapper.mCurrentData.state = newWrapper.getPlaybackState();
-        return newWrapper;
+        mCurrentData = new MediaData(null, null, null);
+        mCurrentData.queue = Util.toMetadataList(getQueue());
+        mCurrentData.metadata = Util.toMetadata(getMetadata());
+        mCurrentData.state = getPlaybackState();
     }
 
     void cleanup() {
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java b/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java
new file mode 100644
index 0000000..d3ef751
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.avrcp;
+
+import android.os.Looper;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provide a method to inject custom MediaControllerWrapper objects for testing. By using the
+ * factory methods instead of calling the wrap method of MediaControllerWrapper directly, we can
+ * inject a custom MediaControllerWrapper that can be used with JUnit and Mockito to set
+ * expectations and validate behaviour in tests.
+ */
+public final class MediaPlayerWrapperFactory {
+    private static MediaPlayerWrapper sInjectedWrapper;
+
+    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
+        if (sInjectedWrapper != null) return sInjectedWrapper;
+        if (controller == null || looper == null) {
+            return null;
+        }
+
+        MediaPlayerWrapper newWrapper;
+        if (controller.getPackageName().equals("com.google.android.music")) {
+            newWrapper = new GPMWrapper(controller, looper);
+        } else {
+            newWrapper = new MediaPlayerWrapper(controller, looper);
+        }
+        return newWrapper;
+    }
+
+    @VisibleForTesting
+    static void inject(MediaPlayerWrapper wrapper) {
+        sInjectedWrapper = wrapper;
+    }
+}
+
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index d7c6469..2343e9f 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -511,7 +511,7 @@
         mAdapterStateMachine =  AdapterState.make(this);
         mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
         mVendorSocket = new VendorSocket(this);
-        initNative(isGuest(), isSingleUserMode());
+        initNative(isGuest(), isNiapMode());
         mNativeAvailable = true;
         mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
         mAppOps = getSystemService(AppOpsManager.class);
@@ -3617,8 +3617,8 @@
         return UserManager.get(this).isGuestUser();
     }
 
-    private boolean isSingleUserMode() {
-        return UserManager.get(this).hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+    private boolean isNiapMode() {
+        return Settings.Global.getInt(getContentResolver(), "niap_mode", 0) == 1;
     }
 
     /**
@@ -3654,7 +3654,7 @@
 
     static native void classInitNative();
 
-    native boolean initNative(boolean startRestricted, boolean isSingleUserMode);
+    native boolean initNative(boolean startRestricted, boolean isNiapMode);
 
     native void cleanupNative();
 
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
index ec8b1f8..a895ece 100644
--- a/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -15,6 +15,7 @@
  */
 package com.android.bluetooth.gatt;
 
+import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanSettings;
 import android.os.Binder;
 import android.os.RemoteException;
@@ -29,10 +30,12 @@
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * ScanStats class helps keep track of information about scans
@@ -44,12 +47,18 @@
 
     static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss");
 
+    static final int OPPORTUNISTIC_WEIGHT = 0;
+    static final int LOW_POWER_WEIGHT = 10;
+    static final int BALANCED_WEIGHT = 25;
+    static final int LOW_LATENCY_WEIGHT = 100;
+
     /* ContextMap here is needed to grab Apps and Connections */ ContextMap mContextMap;
 
     /* GattService is needed to add scan event protos to be dumped later */ GattService
             mGattService;
 
-    /* Battery stats is used to keep track of scans and result stats */ IBatteryStats mBatteryStats;
+    /* Battery stats is used to keep track of scans and result stats */ IBatteryStats
+            mBatteryStats;
 
     class LastScan {
         public long duration;
@@ -57,25 +66,36 @@
         public long suspendStartTime;
         public boolean isSuspended;
         public long timestamp;
-        public boolean opportunistic;
-        public boolean timeout;
-        public boolean background;
-        public boolean filtered;
+        public boolean isOpportunisticScan;
+        public boolean isTimeout;
+        public boolean isBackgroundScan;
+        public boolean isFilterScan;
+        public boolean isCallbackScan;
+        public boolean isBatchScan;
         public int results;
         public int scannerId;
+        public int scanMode;
+        public int scanCallbackType;
+        public String filterString;
 
-        LastScan(long timestamp, long duration, boolean opportunistic, boolean background,
-                boolean filtered, int scannerId) {
-            this.duration = duration;
+        LastScan(long timestamp, boolean isFilterScan, boolean isCallbackScan, int scannerId,
+                int scanMode, int scanCallbackType) {
+            this.duration = 0;
             this.timestamp = timestamp;
-            this.opportunistic = opportunistic;
-            this.background = background;
-            this.filtered = filtered;
+            this.isOpportunisticScan = false;
+            this.isTimeout = false;
+            this.isBackgroundScan = false;
+            this.isFilterScan = isFilterScan;
+            this.isCallbackScan = isCallbackScan;
+            this.isBatchScan = false;
+            this.scanMode = scanMode;
+            this.scanCallbackType = scanCallbackType;
             this.results = 0;
             this.scannerId = scannerId;
             this.suspendDuration = 0;
             this.suspendStartTime = 0;
             this.isSuspended = false;
+            this.filterString = "";
         }
     }
 
@@ -95,11 +115,18 @@
     private int mScansStarted = 0;
     private int mScansStopped = 0;
     public boolean isRegistered = false;
-    private long mMinScanTime = Long.MAX_VALUE;
-    private long mMaxScanTime = 0;
     private long mScanStartTime = 0;
-    private long mTotalScanTime = 0;
+    private long mTotalActiveTime = 0;
     private long mTotalSuspendTime = 0;
+    private long mTotalScanTime = 0;
+    private long mOppScanTime = 0;
+    private long mLowPowerScanTime = 0;
+    private long mBalancedScanTime = 0;
+    private long mLowLantencyScanTime = 0;
+    private int mOppScan = 0;
+    private int mLowPowerScan = 0;
+    private int mBalancedScan = 0;
+    private int mLowLantencyScan = 0;
     private List<LastScan> mLastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT);
     private HashMap<Integer, LastScan> mOngoingScans = new HashMap<Integer, LastScan>();
     public long startTime = 0;
@@ -147,7 +174,8 @@
         return mOngoingScans.get(scannerId);
     }
 
-    synchronized void recordScanStart(ScanSettings settings, boolean filtered, int scannerId) {
+    synchronized void recordScanStart(ScanSettings settings, List<ScanFilter> filters,
+            boolean isFilterScan, boolean isCallbackScan, int scannerId) {
         LastScan existingScan = getScanFromScannerId(scannerId);
         if (existingScan != null) {
             return;
@@ -155,11 +183,36 @@
         this.mScansStarted++;
         startTime = SystemClock.elapsedRealtime();
 
-        LastScan scan = new LastScan(startTime, 0, false, false, filtered, scannerId);
+        LastScan scan = new LastScan(startTime, isFilterScan, isCallbackScan, scannerId,
+                settings.getScanMode(), settings.getCallbackType());
         if (settings != null) {
-            scan.opportunistic = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
-            scan.background =
-                    (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
+            scan.isOpportunisticScan = scan.scanMode == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
+            scan.isBackgroundScan =
+                    (scan.scanCallbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
+            scan.isBatchScan =
+                    settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+                    && settings.getReportDelayMillis() != 0;
+            switch (scan.scanMode) {
+                case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
+                    mOppScan++;
+                    break;
+                case ScanSettings.SCAN_MODE_LOW_POWER:
+                    mLowPowerScan++;
+                    break;
+                case ScanSettings.SCAN_MODE_BALANCED:
+                    mBalancedScan++;
+                    break;
+                case ScanSettings.SCAN_MODE_LOW_LATENCY:
+                    mLowLantencyScan++;
+                    break;
+            }
+        }
+
+        if (isFilterScan) {
+            for (ScanFilter filter : filters) {
+                scan.filterString +=
+                      "\n      └ " + filterToStringWithoutNullParam(filter);
+            }
         }
 
         BluetoothMetricsProto.ScanEvent scanEvent = BluetoothMetricsProto.ScanEvent.newBuilder()
@@ -174,14 +227,15 @@
             mScanStartTime = startTime;
         }
         try {
-            boolean isUnoptimized = !(scan.filtered || scan.background || scan.opportunistic);
+            boolean isUnoptimized =
+                    !(scan.isFilterScan || scan.isBackgroundScan || scan.isOpportunisticScan);
             mBatteryStats.noteBleScanStarted(mWorkSource, isUnoptimized);
         } catch (RemoteException e) {
             /* ignore */
         }
         StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
                 StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
-                scan.filtered, scan.background, scan.opportunistic);
+                scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan);
 
         mOngoingScans.put(scannerId, scan);
     }
@@ -216,16 +270,28 @@
                 .build();
         mGattService.addScanEvent(scanEvent);
 
-        if (!isScanning()) {
-            long totalDuration = stopTime - mScanStartTime;
-            mTotalScanTime += totalDuration;
-            mMinScanTime = Math.min(totalDuration, mMinScanTime);
-            mMaxScanTime = Math.max(totalDuration, mMaxScanTime);
+        mTotalScanTime += scanDuration;
+        long activeDuration = scanDuration - scan.suspendDuration;
+        mTotalActiveTime += activeDuration;
+        switch (scan.scanMode) {
+            case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
+                mOppScanTime += activeDuration;
+                break;
+            case ScanSettings.SCAN_MODE_LOW_POWER:
+                mLowPowerScanTime += activeDuration;
+                break;
+            case ScanSettings.SCAN_MODE_BALANCED:
+                mBalancedScanTime += activeDuration;
+                break;
+            case ScanSettings.SCAN_MODE_LOW_LATENCY:
+                mLowLantencyScanTime += activeDuration;
+                break;
         }
 
         try {
             // Inform battery stats of any results it might be missing on scan stop
-            boolean isUnoptimized = !(scan.filtered || scan.background || scan.opportunistic);
+            boolean isUnoptimized =
+                    !(scan.isFilterScan || scan.isBackgroundScan || scan.isOpportunisticScan);
             mBatteryStats.noteBleScanResults(mWorkSource, scan.results % 100);
             mBatteryStats.noteBleScanStopped(mWorkSource, isUnoptimized);
         } catch (RemoteException e) {
@@ -234,7 +300,7 @@
         StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, scan.results % 100);
         StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
                 StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF,
-                scan.filtered, scan.background, scan.opportunistic);
+                scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan);
     }
 
     synchronized void recordScanSuspend(int scannerId) {
@@ -266,7 +332,7 @@
 
         LastScan scan = getScanFromScannerId(scannerId);
         if (scan != null) {
-            scan.timeout = true;
+            scan.isTimeout = true;
         }
     }
 
@@ -304,146 +370,266 @@
         return initiator;
     }
 
+    private static String filterToStringWithoutNullParam(ScanFilter filter) {
+        String filterString = "BluetoothLeScanFilter [";
+        if (filter.getDeviceName() != null) {
+            filterString += " DeviceName=" + filter.getDeviceName();
+        }
+        if (filter.getDeviceAddress() != null) {
+            filterString += " DeviceAddress=" + filter.getDeviceAddress();
+        }
+        if (filter.getServiceUuid() != null) {
+            filterString += " ServiceUuid=" + filter.getServiceUuid();
+        }
+        if (filter.getServiceUuidMask() != null) {
+            filterString += " ServiceUuidMask=" + filter.getServiceUuidMask();
+        }
+        if (filter.getServiceSolicitationUuid() != null) {
+            filterString += " ServiceSolicitationUuid=" + filter.getServiceSolicitationUuid();
+        }
+        if (filter.getServiceSolicitationUuidMask() != null) {
+            filterString +=
+                  " ServiceSolicitationUuidMask=" + filter.getServiceSolicitationUuidMask();
+        }
+        if (filter.getServiceDataUuid() != null) {
+            filterString += " ServiceDataUuid=" + Objects.toString(filter.getServiceDataUuid());
+        }
+        if (filter.getServiceData() != null) {
+            filterString += " ServiceData=" + Arrays.toString(filter.getServiceData());
+        }
+        if (filter.getServiceDataMask() != null) {
+            filterString += " ServiceDataMask=" + Arrays.toString(filter.getServiceDataMask());
+        }
+        if (filter.getManufacturerId() >= 0) {
+            filterString += " ManufacturerId=" + filter.getManufacturerId();
+        }
+        if (filter.getManufacturerData() != null) {
+            filterString += " ManufacturerData=" + Arrays.toString(filter.getManufacturerData());
+        }
+        if (filter.getManufacturerDataMask() != null) {
+            filterString +=
+                  " ManufacturerDataMask=" +  Arrays.toString(filter.getManufacturerDataMask());
+        }
+        filterString += " ]";
+        return filterString;
+    }
+
+
+    private static String scanModeToString(int scanMode) {
+        switch (scanMode) {
+            case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
+                return "OPPORTUNISTIC";
+            case ScanSettings.SCAN_MODE_LOW_LATENCY:
+                return "LOW_LATENCY";
+            case ScanSettings.SCAN_MODE_BALANCED:
+                return "BALANCED";
+            case ScanSettings.SCAN_MODE_LOW_POWER:
+                return "LOW_POWER";
+            default:
+                return "UNKNOWN(" + scanMode + ")";
+        }
+    }
+
+    private static String callbackTypeToString(int callbackType) {
+        switch (callbackType) {
+            case ScanSettings.CALLBACK_TYPE_ALL_MATCHES:
+                return "ALL_MATCHES";
+            case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
+                return "FIRST_MATCH";
+            case ScanSettings.CALLBACK_TYPE_MATCH_LOST:
+                return "LOST";
+            default:
+                return callbackType == (ScanSettings.CALLBACK_TYPE_FIRST_MATCH
+                    | ScanSettings.CALLBACK_TYPE_MATCH_LOST) ? "[FIRST_MATCH | LOST]" : "UNKNOWN: "
+                    + callbackType;
+        }
+    }
+
     synchronized void dumpToString(StringBuilder sb) {
+        long currentTime = System.currentTimeMillis();
         long currTime = SystemClock.elapsedRealtime();
-        long maxScan = mMaxScanTime;
-        long minScan = mMinScanTime;
+        long Score = 0;
         long scanDuration = 0;
+        long suspendDuration = 0;
+        long activeDuration = 0;
+        long totalActiveTime = mTotalActiveTime;
+        long totalSuspendTime = mTotalSuspendTime;
+        long totalScanTime = mTotalScanTime;
+        long oppScanTime = mOppScanTime;
+        long lowPowerScanTime = mLowPowerScanTime;
+        long balancedScanTime = mBalancedScanTime;
+        long lowLatencyScanTime = mLowLantencyScanTime;
+        int oppScan = mOppScan;
+        int lowPowerScan = mLowPowerScan;
+        int balancedScan = mBalancedScan;
+        int lowLatencyScan = mLowLantencyScan;
 
-        if (isScanning()) {
-            scanDuration = currTime - mScanStartTime;
-        }
-        minScan = Math.min(scanDuration, minScan);
-        maxScan = Math.max(scanDuration, maxScan);
+        if (!mOngoingScans.isEmpty()) {
+            for (Integer key : mOngoingScans.keySet()) {
+                LastScan scan = mOngoingScans.get(key);
+                scanDuration = currTime - scan.timestamp;
 
-        if (minScan == Long.MAX_VALUE) {
-            minScan = 0;
-        }
+                if (scan.isSuspended) {
+                    suspendDuration = currTime - scan.suspendStartTime;
+                    totalSuspendTime += suspendDuration;
+                }
 
-        /*TODO: Average scan time can be skewed for
-         * multiple scan clients. It will show less than
-         * actual value.
-         * */
-        long avgScan = 0;
-        long totalScanTime = mTotalScanTime + scanDuration;
-        if (mScansStarted > 0) {
-            avgScan = totalScanTime / mScansStarted;
+                totalScanTime += scanDuration;
+                totalSuspendTime += suspendDuration;
+                activeDuration = scanDuration - scan.suspendDuration - suspendDuration;
+                totalActiveTime += activeDuration;
+                switch (scan.scanMode) {
+                    case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
+                        oppScanTime += activeDuration;
+                        break;
+                    case ScanSettings.SCAN_MODE_LOW_POWER:
+                        lowPowerScanTime += activeDuration;
+                        break;
+                    case ScanSettings.SCAN_MODE_BALANCED:
+                        balancedScanTime += activeDuration;
+                        break;
+                    case ScanSettings.SCAN_MODE_LOW_LATENCY:
+                        lowLatencyScanTime += activeDuration;
+                        break;
+                }
+            }
         }
+        Score = (oppScanTime * OPPORTUNISTIC_WEIGHT + lowPowerScanTime * LOW_POWER_WEIGHT
+              + balancedScanTime * BALANCED_WEIGHT + lowLatencyScanTime * LOW_LATENCY_WEIGHT) / 100;
 
         sb.append("  " + appName);
         if (isRegistered) {
             sb.append(" (Registered)");
         }
 
-        if (!mLastScans.isEmpty()) {
-            LastScan lastScan = mLastScans.get(mLastScans.size() - 1);
-            if (lastScan.opportunistic) {
-                sb.append(" (Opportunistic)");
-            }
-            if (lastScan.background) {
-                sb.append(" (Background)");
-            }
-            if (lastScan.timeout) {
-                sb.append(" (Forced-Opportunistic)");
-            }
-            if (lastScan.filtered) {
-                sb.append(" (Filtered)");
-            }
-        }
-        sb.append("\n");
+        sb.append("\n  LE scans (started/stopped)                                  : "
+                + mScansStarted + " / " + mScansStopped);
+        sb.append("\n  Scan time in ms (active/suspend/total)                      : "
+                + totalActiveTime + " / " + totalSuspendTime + " / " + totalScanTime);
+        sb.append("\n  Scan time with mode in ms (Opp/LowPower/Balanced/LowLatency): "
+                + oppScanTime + " / " + lowPowerScanTime + " / " + balancedScanTime + " / "
+                + lowLatencyScanTime);
+        sb.append("\n  Scan mode counter (Opp/LowPower/Balanced/LowLatency)        : " + oppScan
+                + " / " + lowPowerScan + " / " + balancedScan + " / " + lowLatencyScan);
+        sb.append("\n  Score                                                       : " + Score);
+        sb.append("\n  Total number of results                                     : " + results);
 
-        sb.append("  LE scans (started/stopped)         : " + mScansStarted + " / " + mScansStopped
-                + "\n");
-        sb.append("  Scan time in ms (min/max/avg/total): " + minScan + " / " + maxScan + " / "
-                + avgScan + " / " + totalScanTime + "\n");
-        if (mTotalSuspendTime != 0) {
-            sb.append("  Total time suspended               : " + mTotalSuspendTime + "ms\n");
-        }
-        sb.append("  Total number of results            : " + results + "\n");
-
-        long currentTime = System.currentTimeMillis();
-        long elapsedRt = SystemClock.elapsedRealtime();
         if (!mLastScans.isEmpty()) {
-            sb.append("  Last " + mLastScans.size() + " scans                       :\n");
+            sb.append("\n  Last " + mLastScans.size()
+                    + " scans                                                :");
 
             for (int i = 0; i < mLastScans.size(); i++) {
                 LastScan scan = mLastScans.get(i);
-                Date timestamp = new Date(currentTime - elapsedRt + scan.timestamp);
-                sb.append("    " + DATE_FORMAT.format(timestamp) + " - ");
+                Date timestamp = new Date(currentTime - currTime + scan.timestamp);
+                sb.append("\n    " + DATE_FORMAT.format(timestamp) + " - ");
                 sb.append(scan.duration + "ms ");
-                if (scan.opportunistic) {
+                if (scan.isOpportunisticScan) {
                     sb.append("Opp ");
                 }
-                if (scan.background) {
+                if (scan.isBackgroundScan) {
                     sb.append("Back ");
                 }
-                if (scan.timeout) {
+                if (scan.isTimeout) {
                     sb.append("Forced ");
                 }
-                if (scan.filtered) {
+                if (scan.isFilterScan) {
                     sb.append("Filter ");
                 }
                 sb.append(scan.results + " results");
-                sb.append(" (" + scan.scannerId + ")");
-                sb.append("\n");
+                sb.append(" (" + scan.scannerId + ") ");
+                if (scan.isCallbackScan) {
+                    sb.append("CB ");
+                } else {
+                    sb.append("PI ");
+                }
+                if (scan.isBatchScan) {
+                    sb.append("Batch Scan");
+                } else {
+                    sb.append("Regular Scan");
+                }
                 if (scan.suspendDuration != 0) {
-                    sb.append("      └" + " Suspended Time: " + scan.suspendDuration + "ms\n");
+                    activeDuration = scan.duration - scan.suspendDuration;
+                    sb.append("\n      └ " + "Suspended Time: " + scan.suspendDuration
+                            + "ms, Active Time: " + activeDuration);
+                }
+                sb.append("\n      └ " + "Scan Config: [ ScanMode="
+                        + scanModeToString(scan.scanMode) + ", callbackType="
+                        + callbackTypeToString(scan.scanCallbackType) + " ]");
+                if (scan.isFilterScan) {
+                    sb.append(scan.filterString);
                 }
             }
         }
 
         if (!mOngoingScans.isEmpty()) {
-            sb.append("  Ongoing scans                      :\n");
+            sb.append("\n  Ongoing scans                                               :");
             for (Integer key : mOngoingScans.keySet()) {
                 LastScan scan = mOngoingScans.get(key);
-                Date timestamp = new Date(currentTime - elapsedRt + scan.timestamp);
-                sb.append("    " + DATE_FORMAT.format(timestamp) + " - ");
-                sb.append((elapsedRt - scan.timestamp) + "ms ");
-                if (scan.opportunistic) {
+                Date timestamp = new Date(currentTime - currTime + scan.timestamp);
+                sb.append("\n    " + DATE_FORMAT.format(timestamp) + " - ");
+                sb.append((currTime - scan.timestamp) + "ms ");
+                if (scan.isOpportunisticScan) {
                     sb.append("Opp ");
                 }
-                if (scan.background) {
+                if (scan.isBackgroundScan) {
                     sb.append("Back ");
                 }
-                if (scan.timeout) {
+                if (scan.isTimeout) {
                     sb.append("Forced ");
                 }
-                if (scan.filtered) {
+                if (scan.isFilterScan) {
                     sb.append("Filter ");
                 }
                 if (scan.isSuspended) {
                     sb.append("Suspended ");
                 }
                 sb.append(scan.results + " results");
-                sb.append(" (" + scan.scannerId + ")");
-                sb.append("\n");
+                sb.append(" (" + scan.scannerId + ") ");
+                if (scan.isCallbackScan) {
+                    sb.append("CB ");
+                } else {
+                    sb.append("PI ");
+                }
+                if (scan.isBatchScan) {
+                    sb.append("Batch Scan");
+                } else {
+                    sb.append("Regular Scan");
+                }
                 if (scan.suspendStartTime != 0) {
-                    long duration = scan.suspendDuration + (scan.isSuspended ? (elapsedRt
+                    long duration = scan.suspendDuration + (scan.isSuspended ? (currTime
                             - scan.suspendStartTime) : 0);
-                    sb.append("      └" + " Suspended Time: " + duration + "ms\n");
+                    activeDuration = scan.duration - scan.suspendDuration;
+                    sb.append("\n      └ " + "Suspended Time:" + scan.suspendDuration
+                            + "ms, Active Time:" + activeDuration);
+                }
+                sb.append("\n      └ " + "Scan Config: [ ScanMode="
+                        + scanModeToString(scan.scanMode) + ", callbackType="
+                        + callbackTypeToString(scan.scanCallbackType) + " ]");
+                if (scan.isFilterScan) {
+                    sb.append(scan.filterString);
                 }
             }
         }
 
         ContextMap.App appEntry = mContextMap.getByName(appName);
         if (appEntry != null && isRegistered) {
-            sb.append("  Application ID                     : " + appEntry.id + "\n");
-            sb.append("  UUID                               : " + appEntry.uuid + "\n");
+            sb.append("\n  Application ID                     : " + appEntry.id);
+            sb.append("\n  UUID                               : " + appEntry.uuid);
 
             List<ContextMap.Connection> connections = mContextMap.getConnectionByApp(appEntry.id);
 
-            sb.append("  Connections: " + connections.size() + "\n");
+            sb.append("\n  Connections: " + connections.size());
 
             Iterator<ContextMap.Connection> ii = connections.iterator();
             while (ii.hasNext()) {
                 ContextMap.Connection connection = ii.next();
-                long connectionTime = elapsedRt - connection.startTime;
-                Date timestamp = new Date(currentTime - elapsedRt + connection.startTime);
-                sb.append("    " + DATE_FORMAT.format(timestamp) + " - ");
+                long connectionTime = currTime - connection.startTime;
+                Date timestamp = new Date(currentTime - currTime + connection.startTime);
+                sb.append("\n    " + DATE_FORMAT.format(timestamp) + " - ");
                 sb.append((connectionTime) + "ms ");
-                sb.append(": " + connection.address + " (" + connection.connId + ")\n");
+                sb.append(": " + connection.address + " (" + connection.connId + ")");
             }
         }
-        sb.append("\n");
+        sb.append("\n\n");
     }
 }
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index f91e6c4..bddb0a5 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -1975,10 +1975,15 @@
                 Utils.checkCallerHasNetworkSetupWizardPermission(this);
 
         AppScanStats app = mScannerMap.getAppScanStatsById(scannerId);
+        ScannerMap.App cbApp = mScannerMap.getById(scannerId);
         if (app != null) {
             scanClient.stats = app;
             boolean isFilteredScan = (filters != null) && !filters.isEmpty();
-            app.recordScanStart(settings, isFilteredScan, scannerId);
+            boolean isCallbackScan = false;
+            if (cbApp != null) {
+                isCallbackScan = cbApp.callback != null;
+            }
+            app.recordScanStart(settings, filters, isFilteredScan, isCallbackScan, scannerId);
         }
 
         mScanManager.startScan(scanClient);
@@ -2004,6 +2009,13 @@
         piInfo.settings = settings;
         piInfo.filters = filters;
         piInfo.callingPackage = callingPackage;
+
+        // Don't start scan if the Pi scan already in mScannerMap.
+        if (mScannerMap.getByContextInfo(piInfo) != null) {
+            Log.d(TAG, "Don't startScan(PI) since the same Pi scan already in mScannerMap.");
+            return;
+        }
+
         ScannerMap.App app = mScannerMap.add(uuid, null, null, piInfo, this);
         app.mUserHandle = UserHandle.of(UserHandle.getCallingUserId());
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
@@ -2041,7 +2053,8 @@
         if (scanStats != null) {
             scanClient.stats = scanStats;
             boolean isFilteredScan = (piInfo.filters != null) && !piInfo.filters.isEmpty();
-            scanStats.recordScanStart(piInfo.settings, isFilteredScan, scannerId);
+            scanStats.recordScanStart(
+                    piInfo.settings, piInfo.filters, isFilteredScan, false, scannerId);
         }
 
         mScanManager.startScan(scanClient);
@@ -3229,6 +3242,22 @@
         return uuids;
     }
 
+    void dumpRegisterId(StringBuilder sb) {
+        sb.append("  Scanner:\n");
+        for (Integer appId : mScannerMap.getAllAppsIds()) {
+            println(sb, "    app_if: " + appId + ", appName: " + mScannerMap.getById(appId).name);
+        }
+        sb.append("  Client:\n");
+        for (Integer appId : mClientMap.getAllAppsIds()) {
+            println(sb, "    app_if: " + appId + ", appName: " + mClientMap.getById(appId).name);
+        }
+        sb.append("  Server:\n");
+        for (Integer appId : mServerMap.getAllAppsIds()) {
+            println(sb, "    app_if: " + appId + ", appName: " + mServerMap.getById(appId).name);
+        }
+        sb.append("\n\n");
+    }
+
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
@@ -3239,7 +3268,10 @@
 
         println(sb, "mMaxScanFilters: " + mMaxScanFilters);
 
-        sb.append("\nGATT Scanner Map\n");
+        sb.append("\nRegistered App\n");
+        dumpRegisterId(sb);
+
+        sb.append("GATT Scanner Map\n");
         mScannerMap.dump(sb);
 
         sb.append("GATT Client Map\n");
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index fa5a525..d666961 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -1077,8 +1077,6 @@
                     break;
                 }
                 case CALL_STATE_CHANGED: {
-                    if (mDeviceSilenced) break;
-
                     boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
 
                     HeadsetCallState callState = (HeadsetCallState) message.obj;
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 6d14e59..914b5b1 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -337,7 +337,10 @@
             if (DBG) {
                 Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful));
             }
-        } catch (IOException e) {
+        } catch (IOException | NullPointerException e) {
+            // Will get NPE if a null mSocket is passed to BluetoothObexTransport.
+            // mSocket can be set to null if an abort() --> closeSocket() was called between
+            // the calls to connectSocket() and connectObexSession().
             Log.w(TAG, "CONNECT Failure " + e.toString());
             closeSocket();
         }
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 82fa163..2d58a60 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -101,7 +101,7 @@
         }
         Assert.assertNotNull(Looper.myLooper());
         AdapterService adapterService = new AdapterService();
-        adapterService.initNative(false /* is_restricted */, false /* is_single_user_mode */);
+        adapterService.initNative(false /* is_restricted */, false /* is_niap_mode */);
         adapterService.cleanupNative();
         HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig();
         Assert.assertNotNull(adapterConfig);
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
index 6567398..3fea6f5 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
@@ -96,7 +96,7 @@
 
         mProfiles = Config.getSupportedProfiles();
 
-        mMockAdapterService.initNative(false /* is_restricted */, false /* is_single_user_mode */);
+        mMockAdapterService.initNative(false /* is_restricted */, false /* is_niap_mode */);
 
         TestUtils.setAdapterService(mMockAdapterService);
 
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java
new file mode 100644
index 0000000..39c8b4c
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.avrcp;
+
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.AudioManager;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Looper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MediaPlayerListTest {
+    private MediaPlayerList mMediaPlayerList;
+
+    private @Captor ArgumentCaptor<AudioManager.AudioPlaybackCallback> mAudioCb;
+    private @Captor ArgumentCaptor<MediaPlayerWrapper.Callback> mPlayerWrapperCb;
+    private @Captor ArgumentCaptor<MediaData> mMediaUpdateData;
+    private @Mock Context mMockContext;
+    private @Mock AvrcpTargetService.ListCallback mAvrcpCallback;
+    private @Mock MediaController mMockController;
+    private @Mock MediaPlayerWrapper mMockPlayerWrapper;
+
+    private final String mFlagDexmarker = System.getProperty("dexmaker.share_classloader", "false");
+    private MediaPlayerWrapper.Callback mActivePlayerCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        if (!mFlagDexmarker.equals("true")) {
+            System.setProperty("dexmaker.share_classloader", "true");
+        }
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        MockitoAnnotations.initMocks(this);
+
+        AudioManager mockAudioManager = mock(AudioManager.class);
+        when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
+
+        MediaSessionManager mMediaSessionManager =
+                (MediaSessionManager) InstrumentationRegistry.getTargetContext()
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+        PackageManager mockPackageManager = mock(PackageManager.class);
+        when(mMockContext.getSystemService(Context.MEDIA_SESSION_SERVICE))
+            .thenReturn(mMediaSessionManager);
+
+        mMediaPlayerList =
+            new MediaPlayerList(Looper.myLooper(), InstrumentationRegistry.getTargetContext());
+
+        when(mMockContext.registerReceiver(any(), any())).thenReturn(null);
+        when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mockPackageManager);
+        List<ResolveInfo> playerList = new ArrayList<ResolveInfo>();
+        when(mockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(null);
+
+        Method method = BrowsablePlayerConnector.class.getDeclaredMethod("setInstanceForTesting",
+                BrowsablePlayerConnector.class);
+        BrowsablePlayerConnector mockConnector = mock(BrowsablePlayerConnector.class);
+        method.setAccessible(true);
+        method.invoke(null, mockConnector);
+        mMediaPlayerList.init(mAvrcpCallback);
+
+        MediaControllerFactory.inject(mMockController);
+        MediaPlayerWrapperFactory.inject(mMockPlayerWrapper);
+
+        doReturn("testPlayer").when(mMockController).getPackageName();
+        when(mMockPlayerWrapper.isMetadataSynced()).thenReturn(false);
+        mMediaPlayerList.setActivePlayer(mMediaPlayerList.addMediaPlayer(mMockController));
+
+        verify(mMockPlayerWrapper).registerCallback(mPlayerWrapperCb.capture());
+        mActivePlayerCallback = mPlayerWrapperCb.getValue();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Method method = BrowsablePlayerConnector.class.getDeclaredMethod("setInstanceForTesting",
+                BrowsablePlayerConnector.class);
+        method.setAccessible(true);
+        method.invoke(null, (BrowsablePlayerConnector) null);
+
+        MediaControllerFactory.inject(null);
+        MediaPlayerWrapperFactory.inject(null);
+        mMediaPlayerList.cleanup();
+        if (!mFlagDexmarker.equals("true")) {
+            System.setProperty("dexmaker.share_classloader", mFlagDexmarker);
+        }
+    }
+
+    private MediaData prepareMediaData(int playbackState) {
+        PlaybackState.Builder builder = new PlaybackState.Builder();
+        builder.setState(playbackState, 0, 1);
+        ArrayList<Metadata> list = new ArrayList<Metadata>();
+        list.add(Util.empty_data());
+        MediaData newData = new MediaData(
+                Util.empty_data(),
+                builder.build(),
+                list);
+
+        return newData;
+    }
+
+    @Test
+    public void testUpdateMeidaDataForAudioPlaybackWhenActivePlayNotPlaying() {
+        // Verify update media data with playing state
+        doReturn(prepareMediaData(PlaybackState.STATE_PAUSED))
+            .when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(true);
+        verify(mAvrcpCallback).run(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals(data.state.getState(), PlaybackState.STATE_PLAYING);
+
+        // verify update media data with current media player media data
+        MediaData currentMediaData = prepareMediaData(PlaybackState.STATE_PAUSED);
+        doReturn(currentMediaData).when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(false);
+        verify(mAvrcpCallback, times(2)).run(mMediaUpdateData.capture());
+        data = mMediaUpdateData.getValue();
+        Assert.assertEquals(data.metadata, currentMediaData.metadata);
+        Assert.assertEquals(data.state.toString(), currentMediaData.state.toString());
+        Assert.assertEquals(data.queue, currentMediaData.queue);
+    }
+
+    @Test
+    public void testUpdateMediaDataForActivePlayerWhenAudioPlaybackIsNotActive() {
+        MediaData currMediaData = prepareMediaData(PlaybackState.STATE_PLAYING);
+        mActivePlayerCallback.mediaUpdatedCallback(currMediaData);
+        verify(mAvrcpCallback).run(currMediaData);
+
+        currMediaData = prepareMediaData(PlaybackState.STATE_PAUSED);
+        mActivePlayerCallback.mediaUpdatedCallback(currMediaData);
+        verify(mAvrcpCallback).run(currMediaData);
+    }
+
+    @Test
+    public void testNotUpdateMediaDataForAudioPlaybackWhenActivePlayerIsPlaying() {
+        // Verify not update media data for Audio Playback when active player is playing
+        doReturn(prepareMediaData(PlaybackState.STATE_PLAYING))
+            .when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(true);
+        mMediaPlayerList.injectAudioPlaybacActive(false);
+        verify(mAvrcpCallback, never()).run(any());
+    }
+
+    @Test
+    public void testNotUpdateMediaDataForActivePlayerWhenAudioPlaybackIsActive() {
+        doReturn(prepareMediaData(PlaybackState.STATE_PLAYING))
+            .when(mMockPlayerWrapper).getCurrentMediaData();
+        mMediaPlayerList.injectAudioPlaybacActive(true);
+        verify(mAvrcpCallback, never()).run(any());
+
+        // Verify not update active player media data when audio playback is active
+        mActivePlayerCallback.mediaUpdatedCallback(prepareMediaData(PlaybackState.STATE_PAUSED));
+        verify(mAvrcpCallback, never()).run(any());
+    }
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
index ee41320..1e41033 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
@@ -138,10 +138,10 @@
      */
     @Test
     public void testNullControllerLooper() {
-        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(null, mThread.getLooper());
+        MediaPlayerWrapper wrapper = MediaPlayerWrapperFactory.wrap(null, mThread.getLooper());
         Assert.assertNull(wrapper);
 
-        wrapper = MediaPlayerWrapper.wrap(mMockController, null);
+        wrapper = MediaPlayerWrapperFactory.wrap(mMockController, null);
         Assert.assertNull(wrapper);
     }
 
@@ -151,7 +151,8 @@
      */
     @Test
     public void testIsReady() {
-        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         Assert.assertTrue(wrapper.isPlaybackStateReady());
         Assert.assertTrue(wrapper.isMetadataReady());
 
@@ -179,7 +180,8 @@
     @Test
     public void testControllerUpdate() {
         // Create the wrapper object and register the looper with the timeout handler
-        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         Assert.assertTrue(wrapper.isPlaybackStateReady());
         Assert.assertTrue(wrapper.isMetadataReady());
         wrapper.registerCallback(mTestCbs);
@@ -213,7 +215,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -275,7 +277,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -319,7 +321,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -348,7 +350,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -375,7 +377,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Call getCurrentQueue() multiple times.
@@ -398,7 +400,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -455,7 +457,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Cleanup the wrapper
@@ -475,7 +477,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -505,7 +507,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -565,7 +567,7 @@
                 InstrumentationRegistry.getInstrumentation()
                         .acquireLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -615,7 +617,7 @@
                 InstrumentationRegistry.getInstrumentation()
                         .acquireLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller