Merge branch 'dev/10/fp2/security-aosp-qt-release' into int/10/fp2

* dev/10/fp2/security-aosp-qt-release:
  Fix URI check in BluetoothOppUtility.java

Change-Id: I9d9a571b9d93dfe41a18cfca4858fea4f8ce8152
diff --git a/Android.mk b/Android.mk
index a34d2a3..1f73091 100644
--- a/Android.mk
+++ b/Android.mk
@@ -29,6 +29,7 @@
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
         androidx.core_core \
+        androidx.legacy_legacy-support-v4 \
         androidx.lifecycle_lifecycle-livedata \
         androidx.room_room-runtime \
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8ce4707..6de6114 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -320,6 +320,16 @@
                 <action android:name="android.media.browse.MediaBrowserService" />
             </intent-filter>
         </service>
+
+        <activity
+            android:name=".BluetoothPrefs"
+            android:exported="@bool/profile_supported_a2dp_sink"
+            android:enabled="@bool/profile_supported_a2dp_sink">
+            <intent-filter>
+                <action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
+            </intent-filter>
+        </activity>
+
         <service
             android:process="@string/process"
             android:name = ".avrcp.AvrcpTargetService"
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 6262622..55b391c 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -684,7 +684,7 @@
 }
 
 static bool initNative(JNIEnv* env, jobject obj, jboolean isGuest,
-                       jboolean isSingleUserMode, jboolean isAtvDevice) {
+                       jboolean isNiapMode, jboolean isAtvDevice) {
   ALOGV("%s", __func__);
 
   android_bluetooth_UidTraffic.clazz =
@@ -700,7 +700,7 @@
 
   int ret = sBluetoothInterface->init(&sBluetoothCallbacks,
                                       isGuest == JNI_TRUE ? 1 : 0,
-                                      isSingleUserMode == JNI_TRUE ? 1 : 0,
+                                      isNiapMode == JNI_TRUE ? 1 : 0,
                                       isAtvDevice == JNI_TRUE ? 1 : 0);
   if (ret != BT_STATUS_SUCCESS) {
     ALOGE("Error while setting the callbacks: %d\n", ret);
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 060fd90..f011684 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-oudio is ontkoppel"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-oudio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Lêers groter as 4 GB kan nie oorgedra word nie"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Koppel aan Bluetooth"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index dbf4aad..0d2f87c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"የብሉቱዝ ኦዲዮ ግንኙነት ተቋርጧል"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"የብሉቱዝ ኦዲዮ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"ከ4 ጊባ በላይ የሆኑ ፋይሎች ሊዛወሩ አይችሉም"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ከብሉቱዝ ጋር ተገናኝ"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 1525a71..2d4e70d 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -26,10 +26,10 @@
     <string name="airplane_error_title" msgid="2683839635115739939">"وضع الطائرة"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"لا يمكنك استخدام البلوتوث في وضع الطائرة."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
-    <string name="bt_enable_line1" msgid="7203551583048149">"لإستخدام خدمات البلوتوث، يجب تشغيل البلوتوث أولاً."</string>
-    <string name="bt_enable_line2" msgid="4341936569415937994">"هل تريد تشغيل البلوتوث الآن؟"</string>
+    <string name="bt_enable_line1" msgid="7203551583048149">"لإستخدام خدمات البلوتوث، يجب تفعيل البلوتوث أولاً."</string>
+    <string name="bt_enable_line2" msgid="4341936569415937994">"هل تريد تفعيل البلوتوث الآن؟"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"إلغاء"</string>
-    <string name="bt_enable_ok" msgid="3432462749994538265">"تشغيل"</string>
+    <string name="bt_enable_ok" msgid="3432462749994538265">"تفعيل"</string>
     <string name="incoming_file_confirm_title" msgid="8139874248612182627">"نقل الملف"</string>
     <string name="incoming_file_confirm_content" msgid="2752605552743148036">"هل تقبل الملف الوارد؟"</string>
     <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"رفض"</string>
@@ -77,7 +77,7 @@
     <string name="not_exist_file" msgid="3489434189599716133">"ليست هناك أي ملفات"</string>
     <string name="not_exist_file_desc" msgid="4059531573790529229">"الملف غير موجود. \n"</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"يرجى الانتظار…"</string>
-    <string name="enabling_progress_content" msgid="4601542238119927904">"جارٍ تشغيل البلوتوث..."</string>
+    <string name="enabling_progress_content" msgid="4601542238119927904">"جارٍ تفعيل البلوتوث..."</string>
     <string name="bt_toast_1" msgid="972182708034353383">"سيتم استلام الملف. تحقق من التقدم في لوحة الإشعارات."</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"لا يمكن تلقي الملف."</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"تم إيقاف استلام الملف من \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
@@ -142,4 +142,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"انقطع الاتصال بالبث الصوتي عبر البلوتوث."</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"بث صوتي عبر البلوتوث"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"يتعذّر نقل الملفات التي يزيد حجمها عن 4 غيغابايت"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"الاتصال ببلوتوث"</string>
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index fd6ab37..a5f682c 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ব্লুটুথ অডিঅ\'ৰ সৈতে সংযোগ বিচ্ছিন্ন কৰা হ’ল"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ব্লুটুথ অডিঅ\'"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"৪ জি. বি. তকৈ ডাঙৰ ফাইল স্থানান্তৰ কৰিব নোৱাৰি"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ব্লুটুথৰ সৈতে সংযোগ কৰক"</string>
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 4abf91f..ea55d17 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio ilə bağlantı kəsildi"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB-dən böyük olan faylları köçürmək mümkün deyil"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'a qoşulun"</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 4d999f3..9ad26e1 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza sa Bluetooth audijom je prekinuta"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ne mogu da se prenose datoteke veće od 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Poveži sa Bluetooth-om"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 0026ea6..3c9dbd6 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-аўдыя адключана"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-аўдыя"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Немагчыма перадаць файлы, большыя за 4 ГБ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Падключыцца да Bluetooth"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 1566a37..4b57342 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудиовръзката през Bluetooth е прекратена"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Аудио през Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Файловете с размер над 4 ГБ не могат да бъдат прехвърлени"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Свързване с Bluetooth"</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 352f359..be01649 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ব্লুটুথ অডিওর সংযোগ বিচ্ছিন্ন হয়েছে"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ব্লুটুথ অডিও"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"৪GB থেকে বড় ফটো ট্রান্সফার করা যাবে না"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ব্লুটুথের সাথে কানেক্ট করুন"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 7f6eca1..3c7dec4 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio je isključen"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nije moguće prenijeti fajlove veće od 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Poveži se na Bluetooth"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 2c6ec73..bddf096 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>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Àudio per Bluetooth desconnectat"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Àudio per Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No es poden transferir fitxers més grans de 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connecta el Bluetooth"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 3e76d2b..9adb80c 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth Audio – odpojeno"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Soubory větší než 4 GB nelze přenést"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Připojit k Bluetooth"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 25819a1..25cf3d7 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyden blev afbrudt"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-lyd"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"File, der er større end 4 GB, kan ikke overføres"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Opret forbindelse til Bluetooth"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index c9e6554..4ffe470 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-Audio-Verbindung aufgehoben"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Dateien mit mehr als 4 GB können nicht übertragen werden"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Mit Bluetooth verbinden"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 1506bd2..e249a38 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Ο ήχος Bluetooth αποσυνδέθηκε"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Ήχος Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Δεν είναι δυνατή η μεταφορά αρχείων που ξεπερνούν τα 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Σύνδεση σε Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index e549cb6..07202ef 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 02178d4..8bbc66c 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎Bluetooth audio disconnected‎‏‎‎‏‎"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎Bluetooth Audio‎‏‎‎‏‎"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎Files bigger than 4GB cannot be transferred‎‏‎‎‏‎"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎Connect to Bluetooth‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 0fecd58..eb95310 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -111,12 +111,12 @@
     <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth: archivos enviados"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth: archivos recibidos"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
-      <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> con error</item>
-      <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> con error</item>
+      <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> incompletos</item>
+      <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> incompleto </item>
     </plurals>
     <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
-      <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> correctos, %2$s</item>
-      <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> correcto, %2$s</item>
+      <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> completos, %2$s</item>
+      <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> completo, %2$s</item>
     </plurals>
     <string name="transfer_menu_clear_all" msgid="790017462957873132">"Eliminar lista"</string>
     <string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth desconectado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No se pueden transferir los archivos de más de 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectarse a Bluetooth"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a9e448c..a67ba6b 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -122,7 +122,7 @@
     <string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
     <string name="transfer_menu_clear" msgid="5854038118831427492">"Borrar de la lista"</string>
     <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Borrar"</string>
-    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Está Sonando"</string>
+    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Reproduciendo"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Guardar"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona las cuentas que quieras compartir por Bluetooth. Tendrás que aceptar cualquier acceso a las cuentas al establecer conexión."</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio por Bluetooth desconectado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio por Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No se pueden transferir archivos de más de 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectarse a un dispositivo Bluetooth"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 3d98cb3..334174f 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -23,7 +23,7 @@
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Tundmatu seade"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Tundmatu"</string>
-    <string name="airplane_error_title" msgid="2683839635115739939">"Lennurežiim"</string>
+    <string name="airplane_error_title" msgid="2683839635115739939">"Lennukirežiim"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"Te ei saa Bluetoothi lennurežiimis kasutada."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
     <string name="bt_enable_line1" msgid="7203551583048149">"Bluetoothi teenuste kasutamiseks peate esmalt Bluetoothi sisse lülitama."</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetoothi heli ühendus on katkestatud"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetoothi heli"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Faile, mis on üle 4 GB, ei saa üle kanda"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Ühenda Bluetoothiga"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 9adbeff..72e6096 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -21,7 +21,7 @@
     <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Jarri onartutakoen zerrendan Bluetooth bidezko gailua."</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth bidezko gailu bat aldi baterako onartutakoen zerrendan jartzeko baimena ematen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth-a"</string>
-    <string name="unknown_device" msgid="9221903979877041009">"Gailu ezezaguna"</string>
+    <string name="unknown_device" msgid="9221903979877041009">"Identifikatu ezin den gailua"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Ezezaguna"</string>
     <string name="airplane_error_title" msgid="2683839635115739939">"Hegaldi modua"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"Ezin duzu erabili Bluetooth-a Hegaldi moduan."</string>
@@ -84,7 +84,7 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hartzaileari fitxategia bidaltzen"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" hartzaileari <xliff:g id="NUMBER">%1$s</xliff:g> fitxategi bidaltzen"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hartzaileari fitxategia bidaltzeari utzi zaio."</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" erabiltzailearen fitxategia gordetzeko behar adina toki USB memorian"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" erabiltzailearen fitxategia gordetzeko behar adina toki USB bidezko memorian"</string>
     <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" erabiltzailearen fitxategia gordetzeko behar adina toki SD txartelean"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Beharrezko memoria: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Eskaera gehiegi prozesatzen ari dira. Saiatu berriro geroago."</string>
@@ -95,7 +95,7 @@
     <string name="status_forbidden" msgid="613956401054050725">"Xede-gailuak transferentzia debekatu du."</string>
     <string name="status_canceled" msgid="6664490318773098285">"Erabiltzaileak bertan behera utzi du transferentzia."</string>
     <string name="status_file_error" msgid="3671917770630165299">"Memoria-arazoa."</string>
-    <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ez dago USB memoriarik."</string>
+    <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ez dago USB bidezko memoriarik."</string>
     <string name="status_no_sd_card_default" msgid="396564893716701954">"Ez dago SD txartelik. Transferitutako fitxategiak gordetzeko, sartu SD txartel bat."</string>
     <string name="status_connection_error" msgid="947681831523219891">"Ezin izan da konektatu."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Ezin da eskaera behar bezala kudeatu."</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Deskonektatu da Bluetooth bidezko audioa"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth bidezko audioa"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ezin dira transferitu 4 GB baino gehiagoko fitxategiak"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Konektatu Bluetooth-era"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c95558e..e2c1b75 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ارتباط بلوتوث صوتی قطع شد"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"بلوتوث‌ صوتی"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"فایل‌های بزرگ‌تر از ۴ گیگابایت نمی‌توانند منتقل شوند"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"اتصال به بلوتوث"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 0fbc55c..f1792c6 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ääni katkaistu"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-ääni"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Yli 4 Gt:n kokoisia tiedostoja ei voi siirtää."</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Muodosta Bluetooth-yhteys"</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index b881cfc..4a62f98 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Les fichiers dépassant 4 Go ne peuvent pas être transférés"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connexion au Bluetooth"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 06fe209..e0caaee 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Impossible de transférer les fichiers supérieurs à 4 Go"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Se connecter au Bluetooth"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 9dedefd..1f2b1d0 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Desconectouse o audio por Bluetooth"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio por Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Non se poden transferir ficheiros de máis de 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectar ao Bluetooth"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index d6e1000..4195292 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"બ્લૂટૂથ ઑડિઓ ડિસ્કનેક્ટ થયું"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"બ્લૂટૂથ ઑડિઓ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB કરતા મોટી ફાઇલ ટ્રાન્સફર કરી શકાતી નથી"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"બ્લૂટૂથ સાથે કનેક્ટ કરો"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 30563fb..cb0bb5f 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड प्रबंधक में पहुंच प्राप्त करें."</string>
+    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड मैनेजर में पहुंच पाएं."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ऐप्लिकेशन को BluetoothShare प्रबंधक के इस्तेमाल की मंज़ूरी देता है और फ़ाइलों को ट्रांसफ़र करने के लिए उसका उपयोग करने देता है."</string>
     <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"श्वेतसूची bluetooth डिवाइस पहुंच."</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ऐप को, ब्लूटूथ डिवाइस को उपयोगकर्ता की पुष्टि के बिना इस डिवाइस पर फ़ाइल भेजने की‍ अनुमति देकर, कुछ देर के लिए अनुमति देता है"</string>
@@ -38,9 +38,9 @@
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" से आने वाली फ़ाइल स्वीकार करते हुए टाइम आउट हो गया."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"आवक फ़ाइल"</string>
     <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> भेजने के लिए तैयार है"</string>
-    <string name="notification_receiving" msgid="4674648179652543984">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त कर रहा है"</string>
-    <string name="notification_received" msgid="3324588019186687985">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त"</string>
-    <string name="notification_received_fail" msgid="3619350997285714746">"ब्लूटूथ शेयर: फ़ाइल <xliff:g id="FILE">%1$s</xliff:g> प्राप्त नहीं हुई"</string>
+    <string name="notification_receiving" msgid="4674648179652543984">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> पा रहा है"</string>
+    <string name="notification_received" msgid="3324588019186687985">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> पाई गई"</string>
+    <string name="notification_received_fail" msgid="3619350997285714746">"ब्लूटूथ शेयर: फ़ाइल <xliff:g id="FILE">%1$s</xliff:g> नहीं मिली"</string>
     <string name="notification_sending" msgid="3035748958534983833">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> भेज रहा है"</string>
     <string name="notification_sent" msgid="9218710861333027778">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> भेजा गया"</string>
     <string name="notification_sent_complete" msgid="302943281067557969">"100% पूरा"</string>
@@ -50,17 +50,17 @@
     <string name="download_line2" msgid="5876973543019417712">"फ़ाइल: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="download_line3" msgid="4384821622908676061">"फ़ाइल आकार: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="download_line4" msgid="8535996869722666525"></string>
-    <string name="download_line5" msgid="3069560415845295386">"फ़ाइल प्राप्त कर रहा है…"</string>
+    <string name="download_line5" msgid="3069560415845295386">"फ़ाइल पा रहा है…"</string>
     <string name="download_cancel" msgid="9177305996747500768">"रोकें"</string>
     <string name="download_ok" msgid="5000360731674466039">"छुपाएं"</string>
     <string name="incoming_line1" msgid="2127419875681087545">"प्रेषक"</string>
     <string name="incoming_line2" msgid="3348994249285315873">"फ़ाइल नाम"</string>
     <string name="incoming_line3" msgid="7954237069667474024">"आकार"</string>
-    <string name="download_fail_line1" msgid="3846450148862894552">"फ़ाइल प्राप्त नहीं हुई"</string>
+    <string name="download_fail_line1" msgid="3846450148862894552">"फ़ाइल नहीं मिली"</string>
     <string name="download_fail_line2" msgid="8950394574689971071">"फ़ाइल: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="download_fail_line3" msgid="3451040656154861722">"कारण: <xliff:g id="REASON">%1$s</xliff:g>"</string>
     <string name="download_fail_ok" msgid="1521733664438320300">"ठीक है"</string>
-    <string name="download_succ_line5" msgid="4509944688281573595">"फ़ाइल प्राप्त की गई"</string>
+    <string name="download_succ_line5" msgid="4509944688281573595">"फ़ाइल मिली"</string>
     <string name="download_succ_ok" msgid="7053688246357050216">"खोलें"</string>
     <string name="upload_line1" msgid="2055952074059709052">"प्रति: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="upload_line3" msgid="4920689672457037437">"फ़ाइल प्रकार: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
@@ -79,8 +79,8 @@
     <string name="enabling_progress_title" msgid="436157952334723406">"कृपया प्रतीक्षा करें..."</string>
     <string name="enabling_progress_content" msgid="4601542238119927904">"ब्लूटूथ चालू कर रहा है…"</string>
     <string name="bt_toast_1" msgid="972182708034353383">"फ़ाइल मिलेगी. सूचना पैनल में प्रगति देखें."</string>
-    <string name="bt_toast_2" msgid="8602553334099066582">"फ़ाइल प्राप्त नहीं की जा सकती."</string>
-    <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" से फ़ाइल प्राप्त करना रोका गया"</string>
+    <string name="bt_toast_2" msgid="8602553334099066582">"फ़ाइल पाई नहीं जा सकती."</string>
+    <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" से फ़ाइल पाना रोका गया"</string>
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" को फ़ाइल भेज रहा है"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" को <xliff:g id="NUMBER">%1$s</xliff:g> फ़ाइलें भेज रहा है"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" को फ़ाइल भेजना रोका गया"</string>
@@ -109,7 +109,7 @@
     <string name="no_transfers" msgid="3482965619151865672">"कुछ भी ट्रांसफ़र नहीं किया गया."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"सूची से सभी आइटम साफ़ कर दिए जाएंगे."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"ब्लूटूथ शेयर: भेजी गई फ़ाइलें"</string>
-    <string name="inbound_noti_title" msgid="4143352641953027595">"ब्लूटूथ शेयर: प्राप्त फ़ाइलें"</string>
+    <string name="inbound_noti_title" msgid="4143352641953027595">"ब्लूटूथ शेयर: पाई गई फ़ाइलें"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
       <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> असफल.</item>
       <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> असफल.</item>
@@ -125,7 +125,7 @@
     <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"अभी चल रहा है"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"सेव करें"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"रद्द करें"</string>
-    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"वे खाते चुनें जिन्हें आप ब्लूटूथ के ज़रिये शेयर करना चाहते हैं. आपको अब भी कनेक्ट करते समय खातों के किसी भी एक्सेस को स्वीकार करना होगा."</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_title" msgid="7420332483392851321">"ब्लूटूथ संदेश साझाकरण सेटिंग"</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लूटूथ ऑडियो डिसकनेक्ट किया गया"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ब्लूटूथ ऑडियो"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 जीबी से बड़ी फ़ाइलें ट्रांसफ़र नहीं की जा सकतीं"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लूटूथ से कनेक्ट करें"</string>
 </resources>
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-hr/strings.xml b/res/values-hr/strings.xml
index d05c7b3..581e1a8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza Bluetooth Audija prekinuta"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Datoteke veće od 4 GB ne mogu se prenijeti"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Povezivanje s Bluetoothom"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 4fa442f..ff93e44 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audió leválasztva"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audió"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"A 4 GB-nál nagyobb fájlokat nem lehet átvinni"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Csatlakozás Bluetooth-eszközhöz"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index bf6fbf3..1fb6d74 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth աուդիոն անջատված է"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth աուդիո"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 ԳԲ-ից մեծ ֆայլերը հնարավոր չէ փոխանցել"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Միանալ Bluetooth-ին"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 15722da..01c4fe1 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio terputus"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"File yang berukuran lebih dari 4GB tidak dapat ditransfer"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Hubungkan ke Bluetooth"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 6246389..65db6e9 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-hljóð aftengt"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-hljóð"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ekki er hægt að flytja skrár sem eru stærri en 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Tengjast við Bluetooth"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e4a486f..27cefda 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth disconnesso"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Impossibile trasferire file con dimensioni superiori a 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connettiti a Bluetooth"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7fe6a4f..11446ff 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>
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"‏אודיו Bluetooth מנותק"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"‏אודיו Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"‏לא ניתן להעביר קבצים שגדולים מ-GB‏ 4"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"‏התחברות באמצעות Bluetooth"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 27ccaac..7688d48 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ダウンロードマネージャーにアクセスします。"</string>
+    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ダウンロード マネージャーにアクセスします。"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShareマネージャーへのアクセスとそれを利用したファイル転送をアプリに許可します。"</string>
     <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetoothデバイスによるアクセスを許可します。"</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetoothデバイスによるアクセスを一時的に許可して、ユーザーの確認を受けずにそのデバイスからこのデバイスにファイルを送信することをアプリに許可します。"</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth オーディオは接続を解除されています"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth オーディオ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB を超えるファイルは転送できません"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth に接続する"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 4983eaa..92b6329 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth აუდიო გათიშულია"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth აუდიო"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 გბაიტზე დიდი მოცულობის ფაილების გადატანა ვერ მოხერხდება"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-თან დაკავშირება"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 82de8a6..811187f 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth дыбысы ажыратылды"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth aудиосы"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Көлемі 4 ГБ-тан асатын файлдар тасымалданбайды"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-ге қосылу"</string>
 </resources>
diff --git a/res/values-kk/strings_pbap.xml b/res/values-kk/strings_pbap.xml
index 29ff221..11893b9 100644
--- a/res/values-kk/strings_pbap.xml
+++ b/res/values-kk/strings_pbap.xml
@@ -4,9 +4,7 @@
     <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"%1$s үшін сессия кілтін теру"</string>
     <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"Bluetooth сессия кілті қажет"</string>
     <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"%1$s арқылы байланыс қабылдау уақыты өтіп кетті"</string>
-    <!-- String.format failed for translation -->
-    <!-- no translation found for pbap_authentication_timeout_message (4166979525521902687) -->
-    <skip />
+    <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"%1$s арқылы сессия кілтін енгізу уақыты өтіп кетті"</string>
     <string name="auth_notif_ticker" msgid="1575825798053163744">"Obex растау талабы"</string>
     <string name="auth_notif_title" msgid="7599854855681573258">"Сессия кілті"</string>
     <string name="auth_notif_message" msgid="6667218116427605038">"%1$s үшін сессия кілтін теру"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index f4b7e7a..1db467c 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -122,7 +122,7 @@
     <string name="transfer_menu_open" msgid="3368984869083107200">"បើក"</string>
     <string name="transfer_menu_clear" msgid="5854038118831427492">"សម្អាត​ពី​បញ្ជី"</string>
     <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"សម្អាត"</string>
-    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
+    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"ឥឡូវកំពុងចាក់"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"រក្សាទុក"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"បោះបង់"</string>
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ជ្រើសគណនីដែលអ្នកចង់ចែករំលែកតាមរយៈប៊្លូធូស។ អ្នកនៅតែត្រូវទទួលយកលទ្ធភាពចូលដំណើរការទាំងឡាយទៅកាន់គណនីនេះដដែល នៅពេលភ្ជាប់។"</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"សំឡេងប៊្លូធូសត្រូវបានផ្តាច់"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"សំឡេងប៊្លូធូស"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"ឯកសារ​ដែល​មាន​ទំហំ​ធំ​ជាង 4 GB មិន​អាចផ្ទេរ​បាន​ទេ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ភ្ជាប់​ប៊្លូធូស"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 0f0e06f..1d87823 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ಗಿಂತ ದೊಡ್ಡದಾದ ಫೈಲ್‌ಗಳನ್ನು ವರ್ಗಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ಬ್ಲೂಟೂತ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f5eeca9..3977271 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"블루투스 오디오가 연결 해제됨"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"블루투스 오디오"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB보다 큰 파일은 전송할 수 없습니다"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"블루투스에 연결"</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 0ed4aa9..f02c874 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -78,7 +78,7 @@
     <string name="not_exist_file_desc" msgid="4059531573790529229">"Мындай файл жок. \n"</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"Күтө туруңуз…"</string>
     <string name="enabling_progress_content" msgid="4601542238119927904">"Bluetooth жандырылууда…"</string>
-    <string name="bt_toast_1" msgid="972182708034353383">"Файл алынат. Эскертмелер тактасынан жүрүшүн байкап турсаңыз болот."</string>
+    <string name="bt_toast_1" msgid="972182708034353383">"Файл алынат. Билдирмелер тактасынан жүрүшүн байкап турсаңыз болот."</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"Файлды алуу мүмкүн эмес."</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды алуу токтотулду"</string>
     <string name="bt_toast_4" msgid="4678812947604395649">"Кийинкиге файл жөнөтүлүүдө: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
@@ -125,7 +125,7 @@
     <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Азыр эмне ойноп жатат?"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Сактоо"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Жокко чыгаруу"</string>
-    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын каттоо эсептерине кирүү мүмкүнчүлүгүн ырастап турушуңуз керек."</string>
+    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын аккаунттарына кирүү мүмкүнчүлүгүн ырастап турушуңуз керек."</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_title" msgid="7420332483392851321">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth аудио ажыратылды"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth аудио"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4Гб чоң файлдарды өткөрүү мүмкүн эмес"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'га туташуу"</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index bebe622..3257841 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ຕັດການເຊື່ອມຕໍ່ສຽງ Bluetooth ແລ້ວ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ສຽງ Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"ບໍ່ສາມາດໂອນຍ້າຍໄຟລ໌ທີ່ໃຫຍກວ່າ 4GB ໄດ້"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ເຊື່ອມຕໍ່ກັບ Bluetooth"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 5d0f759..42562f4 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"„Bluetooth“ garsas atjungtas"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"„Bluetooth“ garsas"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Negalima perkelti didesnių nei 4 GB failų"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Prisijungti prie „Bluetooth“"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 062a019..6edd984 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Pārtraukts savienojums ar Bluetooth audio"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nevar pārsūtīt failus, kas lielāki par 4 GB."</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Izveidot savienojumu ar Bluetooth"</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index f188ae0..5045b56 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -23,7 +23,7 @@
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Непознат уред"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Непознат"</string>
-    <string name="airplane_error_title" msgid="2683839635115739939">"Режим на работа во авион"</string>
+    <string name="airplane_error_title" msgid="2683839635115739939">"Авионски режим"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"Не можете да користите Bluetooth во режим на авион."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
     <string name="bt_enable_line1" msgid="7203551583048149">"За Bluetooth услуги, прво мора да вклучите Bluetooth."</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудиото преку Bluetooth е исклучено"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Аудио преку Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не може да се пренесуваат датотеки поголеми од 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Поврзи се со Bluetooth"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 08e907f..716bf1d 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -20,7 +20,7 @@
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare മാനേജർ ആക്‌സസ്സുചെയ്യാനും ഫയലുകൾ കൈമാറാൻ അത് ഉപയോഗിക്കാനും അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"വൈറ്റ്‌ലിസ്റ്റ് ബ്ലൂടൂത്ത് ഉപകരണ ആക്‌സസ്സ്."</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ഒരു ബ്ലൂടൂത്ത് ഉപകരണം താൽക്കാലികമായി വൈറ്റ്‌ലിസ്റ്റുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു, അത് ഉപയോക്താവിന്റെ സ്ഥിരീകരണമില്ലാതെ ഈ ഉപകരണത്തിലേക്ക് ഫയലുകൾ അയയ്‌ക്കാൻ ആ ഉപകരണത്തെ അനുവദിക്കുന്നു."</string>
-    <string name="bt_share_picker_label" msgid="6268100924487046932">"ബ്ലൂടൂത്ത്"</string>
+    <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"അജ്ഞാത ഉപകരണം"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"അറിയില്ല"</string>
     <string name="airplane_error_title" msgid="2683839635115739939">"ഫ്ലൈറ്റ് മോഡ്"</string>
@@ -132,4 +132,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ഓഡിയോ വിച്ഛേദിച്ചു"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth ഓഡിയോ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB-യിൽ കൂടുതലുള്ള ഫയലുകൾ കൈമാറാനാവില്ല"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-ലേക്ക് കണക്‌റ്റ് ചെയ്യുക"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index aec448c..60f238a 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth аудиог салгасан"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Аудио"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4ГБ-с дээш хэмжээтэй файлыг шилжүүлэх боломжгүй"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-тэй холбогдох"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a7c0f1e..43e0a54 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -127,11 +127,12 @@
     <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>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लूटूथ ऑडिओ डिस्कनेक्ट केला"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ब्लूटूथ ऑडिओ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB हून मोठ्या फायली ट्रान्सफर करता येणार नाहीत"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लूटूथशी कनेक्ट करा"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index ab9c7a9..a0071ca 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth diputuskan sambungannya"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Fail lebih besar daripada 4GB tidak boleh dipindahkan"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Sambung ke Bluetooth"</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 7e7763a..eb35b0f 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ဘလူးတုသ်အသံ မချိတ်ဆက်ထားပါ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ဘလူးတုသ် အသံ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ထက်ပိုကြီးသည့် ဖိုင်များကို လွှဲပြောင်းမရနိုင်ပါ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ဘလူးတုသ်သို့ ချိတ်ဆက်ရန်"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index e0418b1..d6f9e77 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyd er frakoblet"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-lyd"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Filer som er større enn 4 GB, kan ikke overføres"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Koble til Bluetooth"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 639df71..6f9ab58 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लुटुथ सम्बन्धी अडियो यन्त्रलाई विच्छेद गरियो"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ब्लुटुथको अडियो"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"४ जि.बि. भन्दा ठूला फाइलहरूलाई स्थानान्तरण गर्न सकिँदैन"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लुटुथमा जोड्नुहोस्"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 9207378..8f49620 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-audio ontkoppeld"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Bestanden groter dan 4 GB kunnen niet worden overgedragen"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Verbinding maken met bluetooth"</string>
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 8819773..94ad041 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ ବିଚ୍ଛିନ୍ନ କରାଗଲା"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GBରୁ ବଡ଼ ଫାଇଲ୍‌ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍‌ କରାଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ବ୍ଲୁଟୁଥ୍ ସହ ସଂଯୋଗ କରନ୍ତୁ"</string>
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 07cab6a..71c771b 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ਬਲੂਟੁੱਥ  ਆਡੀਓ  ਡਿਸਕਨੈਕਟ ਕੀਤੀ ਗਈ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ਬਲੂਟੁੱਥ  ਆਡੀਓ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ਤੋਂ ਵਧੇਰੇ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਟ੍ਰਾਂਸਫ਼ਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ਬਲੂਟੁੱਥ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 8c4869f..3cfbe0d 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Dźwięk Bluetooth odłączony"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Dźwięk Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nie można przenieść plików przekraczających 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Nawiązywanie połączeń przez Bluetooth"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 09e6781..9eae5c9 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desligado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Áudio Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Não é possível transferir os ficheiros com mais de 4 GB."</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Ligar ao Bluetooth"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 8472432..b4a775e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desconectado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Áudio Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Não é possível transferir arquivos maiores que 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectar ao Bluetooth"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 67f117d..fadc4a6 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio prin Bluetooth deconectat"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio prin Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Fișierele mai mari de 4 GB nu pot fi transferate"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectați-vă la Bluetooth"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 87c1492..6625b83 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Звук через Bluetooth отключен"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Звук через Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Можно перенести только файлы размером до 4 ГБ."</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Подключиться по Bluetooth"</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index ed6e355..bbe1e00 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"බ්ලූටූත් ශ්‍රව්‍යය විසන්ධි කරන ලදී"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"බ්ලූටූත් ශ්‍රව්‍යය"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GBට වඩා විශාල ගොනු මාරු කළ නොහැකිය"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"බ්ලූටූත් වෙත සබඳින්න"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index d189d0d..e28a792 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Rozhranie Bluetooth Audio je odpojené"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Súbory väčšie ako 4 GB sa nedajú preniesť"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Pripojiť k zariadeniu Bluetooth"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 912ba37..d681fb2 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Zvok prek Bluetootha ni povezan"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Zvok prek Bluetootha"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Datotek, večjih od 4 GB, ni mogoče prenesti"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Povezovanje z Bluetoothom"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 1a95dcd..5d1aff3 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audioja e \"bluetooth-it\" e shkëputur"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audioja e \"bluetooth-it\""</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Skedarët më të mëdhenj se 4 GB nuk mund të transferohen"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Lidhu me Bluetooth"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 6bdf67a..25fc24f 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Веза са Bluetooth аудијом је прекинута"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth аудио"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не могу да се преносе датотеке веће од 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Повежи са Bluetooth-ом"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 7c1609f..f243475 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ljud är frånkopplat"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-ljud"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Det går inte att överföra filer som är större än 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Anslut till Bluetooth"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 39f803e..edc03d9 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Imeondoa sauti ya Bluetooth"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Sauti ya Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Haiwezi kutuma faili zinazozidi GB 4"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Unganisha kwenye Bluetooth"</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 54677ac..67d872f 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -127,11 +127,12 @@
     <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">"கணக்கைத் தேர்வுசெய்ய முடியாது. ஸ்லாட்கள் எதுவுமில்லை"</string>
     <string name="bluetooth_connected" msgid="6718623220072656906">"புளூடூத் ஆடியோ இணைக்கப்பட்டது"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"புளூடூத் ஆடியோ துண்டிக்கப்பட்டது"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"புளூடூத் ஆடியோ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4ஜி.பை.க்கு மேலிருக்கும் ஃபைல்களை இடமாற்ற முடியாது"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"புளூடூத் உடன் இணை"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index cb26069..e7f7775 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -36,7 +36,7 @@
     <string name="incoming_file_confirm_ok" msgid="281462442932231475">"అంగీకరిస్తున్నాను"</string>
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"సరే"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన ఇన్‌కమింగ్ ఫైల్‌ను అంగీకరిస్తున్నప్పుడు గడువు సమయం ముగిసింది"</string>
-    <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ఫైల్ స్వీకరణ"</string>
+    <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ఇన్‌క‌మింగ్‌ ఫైల్"</string>
     <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> ఫైల్ పంపడానికి సిద్ధంగా ఉన్నారు"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"బ్లూటూత్ భాగస్వామ్యం: <xliff:g id="FILE">%1$s</xliff:g>ను స్వీకరిస్తోంది"</string>
     <string name="notification_received" msgid="3324588019186687985">"బ్లూటూత్ భాగస్వామ్యం: <xliff:g id="FILE">%1$s</xliff:g> స్వీకరించబడింది"</string>
@@ -104,7 +104,7 @@
     <string name="opp_notification_group" msgid="3486303082135789982">"బ్లూటూత్ భాగస్వామ్యం"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> స్వీకరించడం పూర్తయింది."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> పంపడం పూర్తయింది."</string>
-    <string name="inbound_history_title" msgid="6940914942271327563">"స్వీకృత బదిలీలు"</string>
+    <string name="inbound_history_title" msgid="6940914942271327563">"ఇన్‌బౌండ్ బదిలీలు"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"అవుట్‌బౌండ్ బదిలీలు"</string>
     <string name="no_transfers" msgid="3482965619151865672">"బదిలీ చరిత్ర ఖాళీగా ఉంది."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"జాబితా నుండి అన్ని అంశాలు క్లియర్ చేయబడతాయి."</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"బ్లూటూత్ ఆడియో డిస్‌కనెక్ట్ చేయబడింది"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"బ్లూటూత్ ఆడియో"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB కన్నా పెద్ద ఫైళ్లు బదిలీ చేయబడవు"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"బ్లూటూత్‌కు కనెక్ట్ చేయి"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8cfec4f..48023cd 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ยกเลิกการเชื่อมต่อ Bluetooth Audio แล้ว"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"โอนไฟล์ที่มีขนาดใหญ่กว่า 4 GB ไม่ได้"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"เชื่อมต่อบลูทูธ"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 1f50ceb..3820800 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Nadiskonekta ang Bluetooth audio"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Hindi maililipat ang mga file na mas malaki sa 4GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Kumonekta sa Bluetooth"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 71ce9a4..e1a0bfb 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ses bağlantısı kesildi"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Ses"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB\'tan büyük dosyalar aktarılamaz"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'a bağlan"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index e357d13..f7d6fbd 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -138,4 +138,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудіо Bluetooth від’єднано"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не можна перенести файли, більші за 4 ГБ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Підключитися до Bluetooth"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index cc13746..e944fb8 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"بلوٹوتھ آڈیو غیر منسلک ہے"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"بلوٹوتھ آڈیو"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"‏4GB سے بڑی فائلیں منتقل نہیں کی جا سکتیں"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"بلوٹوتھ سے منسلک کریں"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index dac670b..cadb8be 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth orqali ovoz o‘chirildi"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth orqali ovoz"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GBdan katta hajmli videolar o‘tkazilmaydi"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetoothga ulanish"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 4a40dfe..e0653c0 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -128,10 +128,11 @@
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Chọn tài khoản mà bạn muốn chia sẻ qua Bluetooth. Bạn vẫn phải chấp nhận mọi quyền truy cập vào tài khoản khi kết nối."</string>
     <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Số khe cắm còn lại:"</string>
     <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Biểu tượng ứng dụng"</string>
-    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cài đặt chia sẻ thư qua Bluetooth"</string>
+    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cài đặt cách chia sẻ thư qua Bluetooth"</string>
     <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Không chọn được tài khoản. Còn lại 0 khe cắm"</string>
     <string name="bluetooth_connected" msgid="6718623220072656906">"Đã kết nối âm thanh Bluetooth"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Đã ngắt kết nối âm thanh Bluetooth"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Âm thanh Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Không thể chuyển những tệp lớn hơn 4 GB"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Kết nối với Bluetooth"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4898537..a5f6d2d 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"蓝牙音频已断开连接"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"蓝牙音频"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"无法传输 4GB 以上的文件"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"连接到蓝牙"</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index dfb352a..2881ffe 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"已與藍牙音訊解除連接"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"藍牙音訊"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"無法轉移 4 GB 以上的檔案"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"連接藍牙"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 046dfc6..c8ece01 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"已中斷與藍牙音訊的連線"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"藍牙音訊"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"無法轉移大於 4GB 的檔案"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"使用藍牙連線"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 1bc0fe6..da08ed2 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Umsindo we-Bluetooth unqanyuliwe"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Umsindo we-Bluetooth"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Amafayela amakhulu kuno-4GB awakwazi ukudluliselwa"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Xhumeka ku-Bluetooth"</string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1389e0d..0204f98 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -249,4 +249,5 @@
     <string name="bluetooth_disconnected">Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label">Bluetooth Audio</string>
     <string name="bluetooth_opp_file_limit_exceeded">Files bigger than 4GB cannot be transferred</string>
+    <string name="bluetooth_connect_action">Connect to Bluetooth</string>
 </resources>
diff --git a/res/xml/authenticator.xml b/res/xml/authenticator.xml
index b719fec..ab08a61 100644
--- a/res/xml/authenticator.xml
+++ b/res/xml/authenticator.xml
@@ -17,5 +17,4 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:icon="@mipmap/bt_share"
         android:smallIcon="@mipmap/bt_share"
-        android:accountType="@string/pbap_account_type"
-        android:label="@string/pbap_account_type" />
+        android:accountType="@string/pbap_account_type" />
diff --git a/src/com/android/bluetooth/BluetoothPrefs.java b/src/com/android/bluetooth/BluetoothPrefs.java
new file mode 100644
index 0000000..2c7c87a
--- /dev/null
+++ b/src/com/android/bluetooth/BluetoothPrefs.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that routes to Bluetooth settings when launched
+ */
+public class BluetoothPrefs extends Activity {
+
+    public static final String BLUETOOTH_SETTING_ACTION = "android.settings.BLUETOOTH_SETTINGS";
+    public static final String BLUETOOTH_SETTING_CATEGORY = "android.intent.category.DEFAULT";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent launchIntent = new Intent();
+        launchIntent.setAction(BLUETOOTH_SETTING_ACTION);
+        launchIntent.addCategory(BLUETOOTH_SETTING_CATEGORY);
+        startActivity(launchIntent);
+        finish();
+    }
+}
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 918c2ce..8127e76 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -57,7 +57,7 @@
 
     void setCodecConfigPreference(BluetoothDevice device,
                                   BluetoothCodecStatus codecStatus,
-                                  BluetoothCodecConfig codecConfig) {
+                                  BluetoothCodecConfig newCodecConfig) {
         Objects.requireNonNull(codecStatus);
 
         // Check whether the codecConfig is selectable for this Bluetooth device.
@@ -66,34 +66,36 @@
                 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.");
+            Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing.");
             return;
         }
-        if (!isCodecConfigSelectable(codecConfig, selectableCodecs)) {
-            Log.w(TAG, "Codec is not selectable: " + codecConfig);
+        if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) {
+            Log.w(TAG, "setCodecConfigPreference: invalid codec "
+                    + Objects.toString(newCodecConfig));
             return;
         }
 
         // Check whether the codecConfig would change current codec config.
-        int prioritizedCodecType = getPrioitizedCodecType(codecConfig, selectableCodecs);
+        int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs);
         BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
         if (prioritizedCodecType == currentCodecConfig.getCodecType()
-                && (currentCodecConfig.getCodecType() != codecConfig.getCodecType()
-                || currentCodecConfig.sameAudioFeedingParameters(codecConfig))) {
+                && (prioritizedCodecType != newCodecConfig.getCodecType()
+                || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig)
+                && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) {
             // Same codec with same parameters, no need to send this request to native.
-            Log.i(TAG, "setCodecConfigPreference: codec not changed.");
+            Log.w(TAG, "setCodecConfigPreference: codec not changed.");
             return;
         }
 
         BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
-        codecConfigArray[0] = codecConfig;
+        codecConfigArray[0] = newCodecConfig;
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
     void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
         if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) {
-            Log.i(TAG, "enableOptionalCodecs: already using optional codec: "
-                    + currentCodecConfig.getCodecType());
+            Log.i(TAG, "enableOptionalCodecs: already using optional codec "
+                    + currentCodecConfig.getCodecName());
             return;
         }
 
@@ -115,7 +117,7 @@
 
     void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
         if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) {
-            Log.i(TAG, "disableOptionalCodecs: already using mandatory codec");
+            Log.i(TAG, "disableOptionalCodecs: already using mandatory codec.");
             return;
         }
 
@@ -150,20 +152,6 @@
         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 dbec19b..cebf767 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -712,17 +712,16 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
+            Log.e(TAG, "setCodecConfigPreference: Invalid device");
             return;
         }
-        if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
-            Log.e(TAG, "Cannot set codec config preference: not supported");
+        if (codecConfig == null) {
+            Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
             return;
         }
-
         BluetoothCodecStatus codecStatus = getCodecStatus(device);
         if (codecStatus == null) {
-            Log.e(TAG, "Codec status is null on " + device);
+            Log.e(TAG, "setCodecConfigPreference: Codec status is null");
             return;
         }
         mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig);
@@ -744,16 +743,16 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
+            Log.e(TAG, "enableOptionalCodecs: Invalid device");
             return;
         }
         if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
-            Log.e(TAG, "Cannot enable optional codecs: not supported");
+            Log.e(TAG, "enableOptionalCodecs: No optional codecs");
             return;
         }
         BluetoothCodecStatus codecStatus = getCodecStatus(device);
         if (codecStatus == null) {
-            Log.e(TAG, "Cannot enable optional codecs: codec status is null");
+            Log.e(TAG, "enableOptionalCodecs: Codec status is null");
             return;
         }
         mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
@@ -775,16 +774,16 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
+            Log.e(TAG, "disableOptionalCodecs: Invalid device");
             return;
         }
         if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
-            Log.e(TAG, "Cannot disable optional codecs: not supported");
+            Log.e(TAG, "disableOptionalCodecs: No optional codecs");
             return;
         }
         BluetoothCodecStatus codecStatus = getCodecStatus(device);
         if (codecStatus == null) {
-            Log.e(TAG, "Cannot disable optional codecs: codec status is null");
+            Log.e(TAG, "disableOptionalCodecs: Codec status is null");
             return;
         }
         mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index e0df8b2..cc8f88d 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -596,7 +596,7 @@
 
     boolean isConnected() {
         synchronized (this) {
-            return (getCurrentState() == mConnected);
+            return (getConnectionState() == BluetoothProfile.STATE_CONNECTED);
         }
     }
 
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index e41def0..5271cc7 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -413,7 +413,8 @@
         if (state == StackEvent.AUDIO_STATE_STARTED) {
             mA2dpSinkStreamHandler.obtainMessage(
                     A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
-        } else if (state == StackEvent.AUDIO_STATE_STOPPED) {
+        } else if (state == StackEvent.AUDIO_STATE_STOPPED
+                || state == StackEvent.AUDIO_STATE_REMOTE_SUSPEND) {
             mA2dpSinkStreamHandler.obtainMessage(
                     A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
         }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 19ed87f..77ead16 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -15,6 +15,8 @@
  */
 package com.android.bluetooth.a2dpsink;
 
+import static android.bluetooth.BluetoothProfile.PRIORITY_OFF;
+
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
@@ -168,6 +170,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/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index c24eca9..5aa3cbb 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.a2dpsink;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClientCall;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
@@ -26,6 +27,7 @@
 import android.media.MediaPlayer;
 import android.os.Handler;
 import android.os.Message;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
 import com.android.bluetooth.R;
@@ -181,8 +183,9 @@
                 break;
 
             case AUDIO_FOCUS_CHANGE:
+                mAudioFocus = (int) message.obj;
                 // message.obj is the newly granted audio focus.
-                switch ((int) message.obj) {
+                switch (mAudioFocus) {
                     case AudioManager.AUDIOFOCUS_GAIN:
                         removeMessages(DELAYED_PAUSE);
                         // Begin playing audio, if we paused the remote, send a play now.
@@ -225,7 +228,8 @@
                 break;
 
             case DELAYED_PAUSE:
-                if (mStreamAvailable && !inCallFromStreamingDevice()) {
+                if (BluetoothMediaBrowserService.getPlaybackState()
+                            == PlaybackStateCompat.STATE_PLAYING && !inCallFromStreamingDevice()) {
                     sendAvrcpPause();
                     mSentPause = true;
                     mStreamAvailable = false;
@@ -242,12 +246,9 @@
      */
     private void requestAudioFocusIfNone() {
         if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
-        if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+        if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) {
             requestAudioFocus();
         }
-        // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this.
-        // This function immediately exits if we have a MediaPlayer object.
-        requestMediaKeyFocus();
     }
 
     private synchronized int requestAudioFocus() {
@@ -274,8 +275,11 @@
     }
 
     /**
-     * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
-     * aware of the fact that Bluetooth is playing audio.
+     * Plays a silent audio sample so that MediaSessionService will be aware of the fact that
+     * Bluetooth is playing audio.
+     *
+     * Creates a new MediaPlayer if one does not already exist. Repeat calls to this function are
+     * safe and will result in the silent audio sample again.
      *
      * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
      * chosen to use it.
@@ -283,25 +287,25 @@
     private synchronized void requestMediaKeyFocus() {
         if (DBG) Log.d(TAG, "requestMediaKeyFocus()");
 
-        if (mMediaPlayer != null) return;
-
-        AudioAttributes attrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .build();
-
-        mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
-                mAudioManager.generateAudioSessionId());
         if (mMediaPlayer == null) {
-            Log.e(TAG, "Failed to initialize media player. You may not get media key events");
-            return;
-        }
+            AudioAttributes attrs = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_MEDIA)
+                    .build();
 
-        mMediaPlayer.setLooping(false);
-        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
-            Log.e(TAG, "Silent media player error: " + what + ", " + extra);
-            releaseMediaKeyFocus();
-            return false;
-        });
+            mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
+                    mAudioManager.generateAudioSessionId());
+            if (mMediaPlayer == null) {
+                Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+                return;
+            }
+
+            mMediaPlayer.setLooping(false);
+            mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+                Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+                releaseMediaKeyFocus();
+                return false;
+            });
+        }
 
         mMediaPlayer.start();
         BluetoothMediaBrowserService.setActive(true);
@@ -310,7 +314,6 @@
     private synchronized void abandonAudioFocus() {
         if (DBG) Log.d(TAG, "abandonAudioFocus()");
         stopFluorideStreaming();
-        releaseMediaKeyFocus();
         mAudioManager.abandonAudioFocus(mAudioFocusListener);
         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
     }
@@ -333,9 +336,11 @@
     private void startFluorideStreaming() {
         mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
         mA2dpSinkService.informAudioTrackGainNative(1.0f);
+        requestMediaKeyFocus();
     }
 
     private void stopFluorideStreaming() {
+        releaseMediaKeyFocus();
         mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
     }
 
@@ -359,7 +364,10 @@
         }
         HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
         if (targetDevice != null && headsetClientService != null) {
-            return headsetClientService.getCurrentCalls(targetDevice).size() > 0;
+            List<BluetoothHeadsetClientCall> currentCalls =
+                    headsetClientService.getCurrentCalls(targetDevice);
+            if (currentCalls == null) return false;
+            return currentCalls.size() > 0;
         }
         return false;
     }
diff --git a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index 2f6563d..e2ae494 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothAvrcpTarget;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -96,6 +97,19 @@
 
                 // Update all the playback status info for each connected device
                 mNativeInterface.sendMediaUpdate(false, true, false);
+            } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+                if (mNativeInterface == null) return;
+
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                if (device == null) return;
+
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+                if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                    // If there is no connection, disconnectDevice() will do nothing
+                    if (mNativeInterface.disconnectDevice(device.getAddress())) {
+                        Log.d(TAG, "request to disconnect device " + device);
+                    }
+                }
             }
         }
     }
@@ -166,6 +180,7 @@
         mReceiver = new AvrcpBroadcastReceiver();
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         registerReceiver(mReceiver, filter);
 
         // Only allow the service to be used once it is initialized
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 10d99bf..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);
         }
     };
@@ -626,6 +740,12 @@
                     android.media.session.MediaController controller =
                             new android.media.session.MediaController(mContext, token);
 
+                    if (mMediaSessionManager == null) {
+                        Log.w(TAG, "onAddressedPlayerChanged(Token): Unexpected callback "
+                                + "from the MediaSessionManager");
+                        return;
+                    }
+
                     if (!mMediaPlayerIds.containsKey(controller.getPackageName())) {
                         // Since we have a controller, we can try to to recover by adding the
                         // player and then setting it as active.
@@ -640,6 +760,12 @@
 
                 @Override
                 public void onAddressedPlayerChanged(ComponentName receiver) {
+                    if (mMediaSessionManager == null) {
+                        Log.w(TAG, "onAddressedPlayerChanged(Component): Unexpected callback "
+                                + "from the MediaSessionManager");
+                        return;
+                    }
+
                     if (receiver == null) {
                         return;
                     }
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/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 64e63df..56684cd 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -172,9 +172,11 @@
             }
         }
 
+        // If we don't find a node in the tree then do not have any way to browse for the contents.
+        // Return an empty list instead.
         if (requestedNode == null) {
             if (DBG) Log.d(TAG, "Didn't find a node");
-            return null;
+            return new ArrayList(0);
         } else {
             if (!requestedNode.isCached()) {
                 if (DBG) Log.d(TAG, "node is not cached");
@@ -412,9 +414,10 @@
         if (stateMachine != null) {
             PlayerApplicationSettings supportedSettings =
                     PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS,
+                    supportedSettings);
         }
-        /* Do nothing */
-
     }
 
     private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
@@ -426,10 +429,12 @@
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
 
-            PlayerApplicationSettings desiredSettings =
+            PlayerApplicationSettings currentSettings =
                     PlayerApplicationSettings.makeSettings(playerAttribRsp);
+            stateMachine.sendMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS,
+                    currentSettings);
         }
-        /* Do nothing */
     }
 
     // Browsing related JNI callbacks.
@@ -711,7 +716,7 @@
     /**
      * Send button press commands to addressed device
      *
-     * @param keyCode key code as defined in AVRCP specification
+     * @param keyCode  key code as defined in AVRCP specification
      * @param keyState 0 = key pressed, 1 = key released
      * @return command was sent
      */
@@ -720,7 +725,7 @@
     /**
      * Send group navigation commands
      *
-     * @param keyCode next/previous
+     * @param keyCode  next/previous
      * @param keyState state
      * @return command was sent
      */
@@ -741,7 +746,7 @@
      * Send response to set absolute volume
      *
      * @param absVol new volume
-     * @param label label
+     * @param label  label
      */
     public native void sendAbsVolRspNative(byte[] address, int absVol, int label);
 
@@ -749,8 +754,8 @@
      * Register for any volume level changes
      *
      * @param rspType type of response
-     * @param absVol current volume
-     * @param label label
+     * @param absVol  current volume
+     * @param label   label
      */
     public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
             int label);
@@ -764,7 +769,7 @@
      * Fetch the current now playing list
      *
      * @param start first index to retrieve
-     * @param end last index to retrieve
+     * @param end   last index to retrieve
      */
     public native void getNowPlayingListNative(byte[] address, int start, int end);
 
@@ -772,7 +777,7 @@
      * Fetch the current folder's listing
      *
      * @param start first index to retrieve
-     * @param end last index to retrieve
+     * @param end   last index to retrieve
      */
     public native void getFolderListNative(byte[] address, int start, int end);
 
@@ -780,7 +785,7 @@
      * Fetch the listing of players
      *
      * @param start first index to retrieve
-     * @param end last index to retrieve
+     * @param end   last index to retrieve
      */
     public native void getPlayerListNative(byte[] address, int start, int end);
 
@@ -788,15 +793,15 @@
      * Change the current browsed folder
      *
      * @param direction up/down
-     * @param uid folder unique id
+     * @param uid       folder unique id
      */
     public native void changeFolderPathNative(byte[] address, byte direction, long uid);
 
     /**
      * Play item with provided uid
      *
-     * @param scope scope of item to played
-     * @param uid song unique id
+     * @param scope      scope of item to played
+     * @param uid        song unique id
      * @param uidCounter counter
      */
     public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter);
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 1d1a635..c319364 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -24,10 +24,10 @@
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.Message;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -42,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+
 /**
  * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
  * and interactions with a remote controlable device.
@@ -76,11 +77,15 @@
     static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
     static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
     static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
+    static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217;
+    static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218;
 
     //300->399 Events for Browsing
     static final int MESSAGE_GET_FOLDER_ITEMS = 300;
     static final int MESSAGE_PLAY_ITEM = 301;
     static final int MSG_AVRCP_PASSTHRU = 302;
+    static final int MSG_AVRCP_SET_SHUFFLE = 303;
+    static final int MSG_AVRCP_SET_REPEAT = 304;
 
     static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
 
@@ -89,7 +94,14 @@
      */
     private static final int ABS_VOL_BASE = 127;
 
+    /*
+     * Notification types for Avrcp protocol JNI.
+     */
+    private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
+    private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
+
     private final AudioManager mAudioManager;
+    private final boolean mIsVolumeFixed;
 
     protected final BluetoothDevice mDevice;
     protected final byte[] mDeviceAddress;
@@ -108,6 +120,7 @@
     private int mAddressedPlayerId = -1;
     private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
     private int mVolumeChangedNotificationsToIgnore = 0;
+    private int mVolumeNotificationLabel = -1;
 
     GetFolderList mGetFolderList = null;
 
@@ -137,6 +150,7 @@
         mGetFolderList = new GetFolderList();
         addState(mGetFolderList, mConnected);
         mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
+        mIsVolumeFixed = mAudioManager.isVolumeFixed();
 
         setInitialState(mDisconnected);
     }
@@ -209,26 +223,19 @@
         mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
         BluetoothMediaBrowserService.notifyChanged(mService
                 .sBrowseTree.mRootNode);
-        BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
         mBrowsingConnected = true;
     }
 
     synchronized void onBrowsingDisconnected() {
         if (!mBrowsingConnected) return;
-        mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR);
+        mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR);
         mAddressedPlayer.updateCurrentTrack(null);
         mBrowseTree.mNowPlayingNode.setCached(false);
         BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
-        PlaybackState.Builder pbb = new PlaybackState.Builder();
-        pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
-                1.0f).setActions(0);
-        pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected));
-        BluetoothMediaBrowserService.notifyChanged(pbb.build());
         mService.sBrowseTree.mRootNode.removeChild(
                 mBrowseTree.mRootNode);
         BluetoothMediaBrowserService.notifyChanged(mService
                 .sBrowseTree.mRootNode);
-        BluetoothMediaBrowserService.trackChanged(null);
         mBrowsingConnected = false;
     }
 
@@ -289,8 +296,9 @@
         @Override
         public void enter() {
             if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
-                broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
                 BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
+                BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+                broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
             } else {
                 logD("ReEnteringConnected");
             }
@@ -306,7 +314,14 @@
                     removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
                     sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
                             ABS_VOL_TIMEOUT_MILLIS);
-                    setAbsVolume(msg.arg1, msg.arg2);
+                    handleAbsVolumeRequest(msg.arg1, msg.arg2);
+                    return true;
+
+                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+                    mVolumeNotificationLabel = msg.arg1;
+                    mService.sendRegisterAbsVolRspNative(mDeviceAddress,
+                            NOTIFICATION_RSP_TYPE_INTERIM,
+                            getAbsVolume(), mVolumeNotificationLabel);
                     return true;
 
                 case MESSAGE_GET_FOLDER_ITEMS:
@@ -322,6 +337,14 @@
                     passThru(msg.arg1);
                     return true;
 
+                case MSG_AVRCP_SET_REPEAT:
+                    setRepeat(msg.arg1);
+                    return true;
+
+                case MSG_AVRCP_SET_SHUFFLE:
+                    setShuffle(msg.arg1);
+                    return true;
+
                 case MESSAGE_PROCESS_TRACK_CHANGED:
                     mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
                     BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
@@ -331,11 +354,14 @@
                     mAddressedPlayer.setPlayStatus(msg.arg1);
                     BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                     if (mAddressedPlayer.getPlaybackState().getState()
-                            == PlaybackState.STATE_PLAYING
-                            && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE
-                            && !shouldRequestFocus()) {
+                            == PlaybackStateCompat.STATE_PLAYING
+                            && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE) {
+                        if (shouldRequestFocus()) {
+                            mSessionCallbacks.onPrepare();
+                        } else {
                         sendMessage(MSG_AVRCP_PASSTHRU,
                                 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+                        }
                     }
                     return true;
 
@@ -362,6 +388,18 @@
                     }
                     return true;
 
+                case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
+                    mAddressedPlayer.setSupportedPlayerApplicationSettings(
+                            (PlayerApplicationSettings) msg.obj);
+                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+                    return true;
+
+                case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS:
+                    mAddressedPlayer.setCurrentPlayerApplicationSettings(
+                            (PlayerApplicationSettings) msg.obj);
+                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+                    return true;
+
                 case DISCONNECT:
                     transitionTo(mDisconnecting);
                     return true;
@@ -419,6 +457,20 @@
             return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
                     || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
         }
+
+        private void setRepeat(int repeatMode) {
+            mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
+                    new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{
+                            PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
+                                    PlayerApplicationSettings.REPEAT_STATUS, repeatMode)});
+        }
+
+        private void setShuffle(int shuffleMode) {
+            mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
+                    new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{
+                            PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
+                                    PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)});
+        }
     }
 
     // Handle the get folder listing action
@@ -538,7 +590,7 @@
                 case MESSAGE_GET_FOLDER_ITEMS:
                     if (!mBrowseNode.equals(msg.obj)) {
                         if (shouldAbort(mBrowseNode.getScope(),
-                                 ((BrowseTree.BrowseNode) msg.obj).getScope())) {
+                                ((BrowseTree.BrowseNode) msg.obj).getScope())) {
                             mAbort = true;
                         }
                         deferMessage(msg);
@@ -548,24 +600,9 @@
                     }
                     break;
 
-                case CONNECT:
-                case DISCONNECT:
-                case MSG_AVRCP_PASSTHRU:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_PLAY_ITEM:
-                case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
+                default:
                     // All of these messages should be handled by parent state immediately.
                     return false;
-
-                default:
-                    logD(STATE_TAG + " deferring message " + msg.what
-                                + " to connected!");
-                    deferMessage(msg);
             }
             return true;
         }
@@ -575,8 +612,8 @@
          * necessary.
          *
          * @return true:  a new folder in the same scope
-         *                a new player while fetching contents of a folder
-         *         false: other cases, specifically Now Playing while fetching a folder
+         * a new player while fetching contents of a folder
+         * false: other cases, specifically Now Playing while fetching a folder
          */
         private boolean shouldAbort(int currentScope, int fetchScope) {
             if ((currentScope == fetchScope)
@@ -673,31 +710,70 @@
         @Override
         public void enter() {
             onBrowsingDisconnected();
+            BluetoothMediaBrowserService.trackChanged(null);
+            BluetoothMediaBrowserService.addressedPlayerChanged(null);
             broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
             transitionTo(mDisconnected);
         }
     }
 
+    /**
+     * Handle a request to align our local volume with the volume of a remote device. If
+     * we're assuming the source volume is fixed then a response of ABS_VOL_MAX will always be
+     * sent and no volume adjustment action will be taken on the sink side.
+     *
+     * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
+     * @param label Volume notification label
+     */
+    private void handleAbsVolumeRequest(int absVol, int label) {
+        logD("handleAbsVolumeRequest: absVol = " + absVol + ", label = " + label);
+        if (mIsVolumeFixed) {
+            logD("Source volume is assumed to be fixed, responding with max volume");
+            absVol = ABS_VOL_BASE;
+        } else {
+            mVolumeChangedNotificationsToIgnore++;
+            removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+            sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
+                    ABS_VOL_TIMEOUT_MILLIS);
+            setAbsVolume(absVol);
+        }
+        mService.sendAbsVolRspNative(mDeviceAddress, absVol, label);
+    }
 
-    private void setAbsVolume(int absVol, int label) {
-        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-        int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
-        logD(" setAbsVolume =" + absVol + " maxVol = " + maxVolume
-                + " cur = " + currIndex + " new = " + newIndex);
+    /**
+     * Align our volume with a requested absolute volume level
+     *
+     * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
+     */
+    private void setAbsVolume(int absVol) {
+        int maxLocalVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int curLocalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        int reqLocalVolume = (maxLocalVolume * absVol) / ABS_VOL_BASE;
+        logD("setAbsVolme: absVol = " + absVol + ", reqLocal = " + reqLocalVolume
+                + ", curLocal = " + curLocalVolume + ", maxLocal = " + maxLocalVolume);
+
         /*
          * In some cases change in percentage is not sufficient enough to warrant
          * change in index values which are in range of 0-15. For such cases
          * no action is required
          */
-        if (newIndex != currIndex) {
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
+        if (reqLocalVolume != curLocalVolume) {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, reqLocalVolume,
                     AudioManager.FLAG_SHOW_UI);
         }
-        mService.sendAbsVolRspNative(mDeviceAddress, absVol, label);
     }
 
-    MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
+    private int getAbsVolume() {
+        if (mIsVolumeFixed) {
+            return ABS_VOL_BASE;
+        }
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        int newIndex = (currIndex * ABS_VOL_BASE) / maxVolume;
+        return newIndex;
+    }
+
+    MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {
         @Override
         public void onPlay() {
             logD("onPlay");
@@ -770,6 +846,19 @@
             BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
             sendMessage(MESSAGE_PLAY_ITEM, node);
         }
+
+        @Override
+        public void onSetRepeatMode(int repeatMode) {
+            logD("onSetRepeatMode");
+            sendMessage(MSG_AVRCP_SET_REPEAT, repeatMode);
+        }
+
+        @Override
+        public void onSetShuffleMode(int shuffleMode) {
+            logD("onSetShuffleMode");
+            sendMessage(MSG_AVRCP_SET_SHUFFLE, shuffleMode);
+
+        }
     };
 
     protected void broadcastConnectionStateChanged(int currentState) {
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index bed38d9..4736acf 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -17,8 +17,9 @@
 package com.android.bluetooth.avrcpcontroller;
 
 import android.media.MediaMetadata;
-import android.media.session.PlaybackState;
 import android.os.SystemClock;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
 import java.util.Arrays;
@@ -41,27 +42,31 @@
     public static final int FEATURE_PREVIOUS = 48;
     public static final int FEATURE_BROWSING = 59;
 
-    private int mPlayStatus = PlaybackState.STATE_NONE;
-    private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+    private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
+    private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
     private long mPlayTimeUpdate = 0;
     private float mPlaySpeed = 1;
     private int mId;
     private String mName = "";
     private int mPlayerType;
-    private byte[] mPlayerFeatures;
-    private long mAvailableActions;
+    private byte[] mPlayerFeatures = new byte[16];
+    private long mAvailableActions = PlaybackStateCompat.ACTION_PREPARE;
     private MediaMetadata mCurrentTrack;
-    private PlaybackState mPlaybackState;
+    private PlaybackStateCompat mPlaybackStateCompat;
+    private PlayerApplicationSettings mSupportedPlayerApplicationSettings =
+            new PlayerApplicationSettings();
+    private PlayerApplicationSettings mCurrentPlayerApplicationSettings;
 
     AvrcpPlayer() {
         mId = INVALID_ID;
         //Set Default Actions in case Player data isn't available.
-        mAvailableActions = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
-                | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackState.ACTION_STOP;
-        PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+        mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY
+                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PREPARE;
+        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
                 .setActions(mAvailableActions);
-        mPlaybackState = playbackStateBuilder.build();
+        mPlaybackStateCompat = playbackStateBuilder.build();
     }
 
     AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) {
@@ -70,10 +75,10 @@
         mPlayStatus = playStatus;
         mPlayerType = playerType;
         mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
-        updateAvailableActions();
-        PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
                 .setActions(mAvailableActions);
-        mPlaybackState = playbackStateBuilder.build();
+        mPlaybackStateCompat = playbackStateBuilder.build();
+        updateAvailableActions();
     }
 
     public int getId() {
@@ -87,7 +92,8 @@
     public void setPlayTime(int playTime) {
         mPlayTime = playTime;
         mPlayTimeUpdate = SystemClock.elapsedRealtime();
-        mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+        mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState(
+                mPlayStatus, mPlayTime,
                 mPlaySpeed).build();
     }
 
@@ -97,30 +103,48 @@
 
     public void setPlayStatus(int playStatus) {
         mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
-                - mPlaybackState.getLastPositionUpdateTime());
+                - mPlaybackStateCompat.getLastPositionUpdateTime());
         mPlayStatus = playStatus;
         switch (mPlayStatus) {
-            case PlaybackState.STATE_STOPPED:
+            case PlaybackStateCompat.STATE_STOPPED:
                 mPlaySpeed = 0;
                 break;
-            case PlaybackState.STATE_PLAYING:
+            case PlaybackStateCompat.STATE_PLAYING:
                 mPlaySpeed = 1;
                 break;
-            case PlaybackState.STATE_PAUSED:
+            case PlaybackStateCompat.STATE_PAUSED:
                 mPlaySpeed = 0;
                 break;
-            case PlaybackState.STATE_FAST_FORWARDING:
+            case PlaybackStateCompat.STATE_FAST_FORWARDING:
                 mPlaySpeed = 3;
                 break;
-            case PlaybackState.STATE_REWINDING:
+            case PlaybackStateCompat.STATE_REWINDING:
                 mPlaySpeed = -3;
                 break;
         }
 
-        mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+        mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState(
+                mPlayStatus, mPlayTime,
                 mPlaySpeed).build();
     }
 
+    public void setSupportedPlayerApplicationSettings(
+            PlayerApplicationSettings playerApplicationSettings) {
+        mSupportedPlayerApplicationSettings = playerApplicationSettings;
+        updateAvailableActions();
+    }
+
+    public void setCurrentPlayerApplicationSettings(
+            PlayerApplicationSettings playerApplicationSettings) {
+        Log.d(TAG, "Settings changed");
+        mCurrentPlayerApplicationSettings = playerApplicationSettings;
+        MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
+        session.setRepeatMode(mCurrentPlayerApplicationSettings.getSetting(
+                PlayerApplicationSettings.REPEAT_STATUS));
+        session.setShuffleMode(mCurrentPlayerApplicationSettings.getSetting(
+                PlayerApplicationSettings.SHUFFLE_STATUS));
+    }
+
     public int getPlayStatus() {
         return mPlayStatus;
     }
@@ -131,17 +155,22 @@
         return (mPlayerFeatures[byteNumber] & bitMask) == bitMask;
     }
 
-    public PlaybackState getPlaybackState() {
+    public boolean supportsSetting(int settingType, int settingValue) {
+        return mSupportedPlayerApplicationSettings.supportsSetting(settingType, settingValue);
+    }
+
+    public PlaybackStateCompat getPlaybackState() {
         if (DBG) {
             Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime);
         }
-        return mPlaybackState;
+        return mPlaybackStateCompat;
     }
 
     public synchronized void updateCurrentTrack(MediaMetadata update) {
         if (update != null) {
             long trackNumber = update.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
-            mPlaybackState = new PlaybackState.Builder(mPlaybackState).setActiveQueueItemId(
+            mPlaybackStateCompat = new PlaybackStateCompat.Builder(
+                    mPlaybackStateCompat).setActiveQueueItemId(
                     trackNumber - 1).build();
         }
         mCurrentTrack = update;
@@ -153,26 +182,37 @@
 
     private void updateAvailableActions() {
         if (supportsFeature(FEATURE_PLAY)) {
-            mAvailableActions = mAvailableActions | PlaybackState.ACTION_PLAY;
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PLAY;
         }
         if (supportsFeature(FEATURE_STOP)) {
-            mAvailableActions = mAvailableActions | PlaybackState.ACTION_STOP;
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_STOP;
         }
         if (supportsFeature(FEATURE_PAUSE)) {
-            mAvailableActions = mAvailableActions | PlaybackState.ACTION_PAUSE;
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PAUSE;
         }
         if (supportsFeature(FEATURE_REWIND)) {
-            mAvailableActions = mAvailableActions | PlaybackState.ACTION_REWIND;
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_REWIND;
         }
         if (supportsFeature(FEATURE_FAST_FORWARD)) {
-            mAvailableActions = mAvailableActions | PlaybackState.ACTION_FAST_FORWARD;
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_FAST_FORWARD;
         }
         if (supportsFeature(FEATURE_FORWARD)) {
-            mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_NEXT;
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
         }
         if (supportsFeature(FEATURE_PREVIOUS)) {
-            mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
         }
+        if (mSupportedPlayerApplicationSettings.supportsSetting(
+                PlayerApplicationSettings.REPEAT_STATUS)) {
+            mAvailableActions |= PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
+        }
+        if (mSupportedPlayerApplicationSettings.supportsSetting(
+                PlayerApplicationSettings.SHUFFLE_STATUS)) {
+            mAvailableActions |= PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE;
+        }
+        mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat)
+                .setActions(mAvailableActions).build();
+
         if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions);
     }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 3504cd4..a0b1224 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -16,15 +16,22 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
+import android.app.PendingIntent;
+import android.content.Intent;
 import android.media.MediaMetadata;
 import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
 import android.os.Bundle;
-import android.service.media.MediaBrowserService;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
+import androidx.media.MediaBrowserServiceCompat;
+
+import com.android.bluetooth.BluetoothPrefs;
 import com.android.bluetooth.R;
 
 import java.util.ArrayList;
@@ -37,45 +44,48 @@
  * The applications are expected to use MediaBrowser (see API) and all the music
  * browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
  *
- * The current behavior of MediaSession exposed by this service is as follows:
- * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is
- * connected and first starts playing. Before it starts playing we do not active the session.
+ * The current behavior of MediaSessionCompat exposed by this service is as follows:
+ * 1. MediaSessionCompat is active (i.e. SystemUI and other overview UIs can see updates) when
+ * device is connected and first starts playing. Before it starts playing we do not activate the
+ * session.
  * 1.1 The session is active throughout the duration of connection.
  * 2. The session is de-activated when the device disconnects. It will be connected again when (1)
  * happens.
  */
-public class BluetoothMediaBrowserService extends MediaBrowserService {
+public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
     private static final String TAG = "BluetoothMediaBrowserService";
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
 
-    private MediaSession mSession;
+    private MediaSessionCompat mSession;
 
     // Browsing related structures.
-    private List<MediaSession.QueueItem> mMediaQueue = new ArrayList<>();
+    private List<MediaSessionCompat.QueueItem> mMediaQueue = new ArrayList<>();
+
+    // Error messaging extras
+    public static final String ERROR_RESOLUTION_ACTION_INTENT =
+            "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
+    public static final String ERROR_RESOLUTION_ACTION_LABEL =
+            "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
 
     /**
-     * Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and
-     * MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
+     * Initialize this BluetoothMediaBrowserService, creating our MediaSessionCompat, MediaPlayer
+     * and MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
      */
     @Override
     public void onCreate() {
         if (DBG) Log.d(TAG, "onCreate");
         super.onCreate();
 
-        // Create and configure the MediaSession
-        mSession = new MediaSession(this, TAG);
+        // Create and configure the MediaSessionCompat
+        mSession = new MediaSessionCompat(this, TAG);
         setSessionToken(mSession.getSessionToken());
-        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
+                | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
         mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
         mSession.setQueue(mMediaQueue);
-        PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder();
-        playbackStateBuilder.setState(PlaybackState.STATE_ERROR,
-                PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0);
-        playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected));
-        mSession.setPlaybackState(playbackStateBuilder.build());
+        setErrorPlaybackState();
         sBluetoothMediaBrowserService = this;
     }
 
@@ -89,11 +99,30 @@
         }
     }
 
+    private void setErrorPlaybackState() {
+        Bundle extras = new Bundle();
+        extras.putString(ERROR_RESOLUTION_ACTION_LABEL,
+                getString(R.string.bluetooth_connect_action));
+        Intent launchIntent = new Intent();
+        launchIntent.setAction(BluetoothPrefs.BLUETOOTH_SETTING_ACTION);
+        launchIntent.addCategory(BluetoothPrefs.BLUETOOTH_SETTING_CATEGORY);
+        PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0,
+                launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+        extras.putParcelable(ERROR_RESOLUTION_ACTION_INTENT, pendingIntent);
+        PlaybackStateCompat errorState = new PlaybackStateCompat.Builder()
+                .setErrorMessage(getString(R.string.bluetooth_disconnected))
+                .setExtras(extras)
+                .setState(PlaybackStateCompat.STATE_ERROR, 0, 0)
+                .build();
+        mSession.setPlaybackState(errorState);
+    }
+
     @Override
     public synchronized void onLoadChildren(final String parentMediaId,
-            final Result<List<MediaItem>> result) {
+            final Result<List<MediaBrowserCompat.MediaItem>> result) {
         if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
-        List<MediaItem> contents = getContents(parentMediaId);
+        List<MediaBrowserCompat.MediaItem> contents =
+                MediaBrowserCompat.MediaItem.fromMediaItemList(getContents(parentMediaId));
         if (contents == null) {
             result.detach();
         } else {
@@ -112,7 +141,8 @@
         mMediaQueue.clear();
         if (songList != null) {
             for (MediaItem song : songList) {
-                mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(),
+                mMediaQueue.add(new MediaSessionCompat.QueueItem(
+                        MediaDescriptionCompat.fromMediaDescription(song.getDescription()),
                         mMediaQueue.size()));
             }
         }
@@ -129,8 +159,11 @@
         }
     }
 
-    static synchronized void addressedPlayerChanged(MediaSession.Callback callback) {
+    static synchronized void addressedPlayerChanged(MediaSessionCompat.Callback callback) {
         if (sBluetoothMediaBrowserService != null) {
+            if (callback == null) {
+                sBluetoothMediaBrowserService.setErrorPlaybackState();
+            }
             sBluetoothMediaBrowserService.mSession.setCallback(callback);
         } else {
             Log.w(TAG, "addressedPlayerChanged Unavailable");
@@ -139,13 +172,14 @@
 
     static synchronized void trackChanged(MediaMetadata mediaMetadata) {
         if (sBluetoothMediaBrowserService != null) {
-            sBluetoothMediaBrowserService.mSession.setMetadata(mediaMetadata);
+            sBluetoothMediaBrowserService.mSession.setMetadata(
+                    MediaMetadataCompat.fromMediaMetadata(mediaMetadata));
         } else {
             Log.w(TAG, "trackChanged Unavailable");
         }
     }
 
-    static synchronized void notifyChanged(PlaybackState playbackState) {
+    static synchronized void notifyChanged(PlaybackStateCompat playbackState) {
         Log.d(TAG, "notifyChanged PlaybackState" + playbackState);
         if (sBluetoothMediaBrowserService != null) {
             sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState);
@@ -177,9 +211,23 @@
     }
 
     /**
+     * Get playback state
+     */
+    public static synchronized int getPlaybackState() {
+        if (sBluetoothMediaBrowserService != null) {
+            PlaybackStateCompat currentPlaybackState =
+                    sBluetoothMediaBrowserService.mSession.getController().getPlaybackState();
+            if (currentPlaybackState != null) {
+                return currentPlaybackState.getState();
+            }
+        }
+        return PlaybackStateCompat.STATE_ERROR;
+    }
+
+    /**
      * Get object for controlling playback
      */
-    public static synchronized MediaController.TransportControls getTransportControls() {
+    public static synchronized MediaControllerCompat.TransportControls getTransportControls() {
         if (sBluetoothMediaBrowserService != null) {
             return sBluetoothMediaBrowserService.mSession.getController().getTransportControls();
         } else {
@@ -198,4 +246,16 @@
             Log.w(TAG, "setActive Unavailable");
         }
     }
+
+    /**
+     * Get Media session for updating state
+     */
+    public static synchronized MediaSessionCompat getSession() {
+        if (sBluetoothMediaBrowserService != null) {
+            return sBluetoothMediaBrowserService.mSession;
+        } else {
+            Log.w(TAG, "getSession Unavailable");
+            return null;
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index accea2a..923282d 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -100,7 +100,7 @@
     }
 
     BrowseNode getTrackFromNowPlayingList(int trackNumber) {
-        return mNowPlayingNode.mChildren.get(trackNumber);
+        return mNowPlayingNode.getChild(trackNumber);
     }
 
     // Each node of the tree is represented by Folder ID, Folder Name and the children.
@@ -218,6 +218,13 @@
             return mChildren;
         }
 
+        synchronized BrowseNode getChild(int index) {
+            if (index < 0 || index >= mChildren.size()) {
+                return null;
+            }
+            return mChildren.get(index);
+        }
+
         synchronized BrowseNode getParent() {
             return mParent;
         }
diff --git a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
index c34a2d7..362548e 100644
--- a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
+++ b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
@@ -16,12 +16,11 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
 
 /*
  * Contains information Player Application Setting extended from BluetootAvrcpPlayerSettings
@@ -32,10 +31,10 @@
     /*
      * Values for SetPlayerApplicationSettings from AVRCP Spec V1.6 Appendix F.
      */
-    private static final byte JNI_ATTRIB_EQUALIZER_STATUS = 0x01;
-    private static final byte JNI_ATTRIB_REPEAT_STATUS = 0x02;
-    private static final byte JNI_ATTRIB_SHUFFLE_STATUS = 0x03;
-    private static final byte JNI_ATTRIB_SCAN_STATUS = 0x04;
+    static final byte EQUALIZER_STATUS = 0x01;
+    static final byte REPEAT_STATUS = 0x02;
+    static final byte SHUFFLE_STATUS = 0x03;
+    static final byte SCAN_STATUS = 0x04;
 
     private static final byte JNI_EQUALIZER_STATUS_OFF = 0x01;
     private static final byte JNI_EQUALIZER_STATUS_ON = 0x02;
@@ -55,18 +54,17 @@
 
     private static final byte JNI_STATUS_INVALID = -1;
 
-
     /*
      * Hash map of current settings.
      */
-    private Map<Integer, Integer> mSettings = new HashMap<Integer, Integer>();
+    private SparseArray<Integer> mSettings = new SparseArray<>();
 
     /*
      * Hash map of supported values, a setting should be supported by the remote in order to enable
      * in mSettings.
      */
-    private Map<Integer, ArrayList<Integer>> mSupportedValues =
-            new HashMap<Integer, ArrayList<Integer>>();
+    private SparseArray<ArrayList<Integer>> mSupportedValues =
+            new SparseArray<ArrayList<Integer>>();
 
     /* Convert from JNI array to Java classes. */
     static PlayerApplicationSettings makeSupportedSettings(byte[] btAvrcpAttributeList) {
@@ -82,8 +80,7 @@
                     supportedValues.add(
                             mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
                 }
-                newObj.mSupportedValues.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
-                        supportedValues);
+                newObj.mSupportedValues.put(attrId, supportedValues);
             }
         } catch (ArrayIndexOutOfBoundsException exception) {
             Log.e(TAG, "makeSupportedSettings attributeList index error.");
@@ -91,25 +88,13 @@
         return newObj;
     }
 
-    public BluetoothAvrcpPlayerSettings getAvrcpSettings() {
-        int supportedSettings = 0;
-        for (Integer setting : mSettings.keySet()) {
-            supportedSettings |= setting;
-        }
-        BluetoothAvrcpPlayerSettings result = new BluetoothAvrcpPlayerSettings(supportedSettings);
-        for (Integer setting : mSettings.keySet()) {
-            result.addSettingValue(setting, mSettings.get(setting));
-        }
-        return result;
-    }
-
     static PlayerApplicationSettings makeSettings(byte[] btAvrcpAttributeList) {
         PlayerApplicationSettings newObj = new PlayerApplicationSettings();
         try {
             for (int i = 0; i < btAvrcpAttributeList.length; ) {
                 byte attrId = btAvrcpAttributeList[i++];
 
-                newObj.mSettings.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
+                newObj.mSettings.put(attrId,
                         mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
             }
         } catch (ArrayIndexOutOfBoundsException exception) {
@@ -123,177 +108,69 @@
         mSupportedValues = updates.mSupportedValues;
     }
 
-    public void setValues(BluetoothAvrcpPlayerSettings updates) {
-        int supportedSettings = updates.getSettings();
-        for (int i = 1; i <= BluetoothAvrcpPlayerSettings.SETTING_SCAN; i++) {
-            if ((i & supportedSettings) > 0) {
-                mSettings.put(i, updates.getSettingValue(i));
-            }
-        }
+    public boolean supportsSetting(int settingType, int settingValue) {
+        if (null == mSupportedValues.get(settingType)) return false;
+        return mSupportedValues.valueAt(settingType).contains(settingValue);
     }
 
-    /*
-     * Check through all settings to ensure that they are all available to be set and then check
-     * that the desired value is in fact supported by our remote player.
-     */
-    public boolean supportsSettings(BluetoothAvrcpPlayerSettings settingsToCheck) {
-        int settingSubset = settingsToCheck.getSettings();
-        int supportedSettings = 0;
-        for (Integer setting : mSupportedValues.keySet()) {
-            supportedSettings |= setting;
-        }
-        try {
-            if ((supportedSettings & settingSubset) == settingSubset) {
-                for (Integer settingId : mSettings.keySet()) {
-                    // The setting is in both settings to check and supported settings but the
-                    // value is not supported.
-                    if ((settingId & settingSubset) == settingId && (!mSupportedValues.get(
-                            settingId).contains(settingsToCheck.getSettingValue(settingId)))) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-        } catch (NullPointerException e) {
-            Log.e(TAG,
-                    "supportsSettings received a supported setting that has no supported values.");
-        }
-        return false;
+    public boolean supportsSetting(int settingType) {
+        return (null != mSupportedValues.get(settingType));
     }
 
-    // Convert currently desired settings into an attribute array to pass to the native layer to
-    // enable them.
-    public ArrayList<Byte> getNativeSettings() {
-        int i = 0;
-        ArrayList<Byte> attribArray = new ArrayList<Byte>();
-        for (Integer settingId : mSettings.keySet()) {
-            switch (settingId) {
-                case BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER:
-                    attribArray.add(JNI_ATTRIB_EQUALIZER_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                case BluetoothAvrcpPlayerSettings.SETTING_REPEAT:
-                    attribArray.add(JNI_ATTRIB_REPEAT_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                case BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE:
-                    attribArray.add(JNI_ATTRIB_SHUFFLE_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                case BluetoothAvrcpPlayerSettings.SETTING_SCAN:
-                    attribArray.add(JNI_ATTRIB_SCAN_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
-                            mSettings.get(settingId)));
-                    break;
-                default:
-                    Log.w(TAG, "Unknown setting found in getNativeSettings: " + settingId);
-            }
-        }
-        return attribArray;
+    public int getSetting(int settingType) {
+        if (null == mSettings.get(settingType)) return -1;
+        return mSettings.get(settingType);
     }
 
     // Convert a native Attribute Id/Value pair into the AVRCP equivalent value.
     private static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) {
-        if (attribId == JNI_ATTRIB_EQUALIZER_STATUS) {
-            switch (attribVal) {
-                case JNI_EQUALIZER_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
-                case JNI_EQUALIZER_STATUS_ON:
-                    return BluetoothAvrcpPlayerSettings.STATE_ON;
-            }
-        } else if (attribId == JNI_ATTRIB_REPEAT_STATUS) {
+        if (attribId == REPEAT_STATUS) {
             switch (attribVal) {
                 case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT:
-                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+                    return PlaybackStateCompat.REPEAT_MODE_ALL;
                 case JNI_REPEAT_STATUS_GROUP_REPEAT:
-                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+                    return PlaybackStateCompat.REPEAT_MODE_GROUP;
                 case JNI_REPEAT_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
+                    return PlaybackStateCompat.REPEAT_MODE_NONE;
                 case JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT:
-                    return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
+                    return PlaybackStateCompat.REPEAT_MODE_ONE;
             }
-        } else if (attribId == JNI_ATTRIB_SCAN_STATUS) {
-            switch (attribVal) {
-                case JNI_SCAN_STATUS_ALL_TRACK_SCAN:
-                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
-                case JNI_SCAN_STATUS_GROUP_SCAN:
-                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
-                case JNI_SCAN_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
-            }
-        } else if (attribId == JNI_ATTRIB_SHUFFLE_STATUS) {
+        } else if (attribId == SHUFFLE_STATUS) {
             switch (attribVal) {
                 case JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
-                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+                    return PlaybackStateCompat.SHUFFLE_MODE_ALL;
                 case JNI_SHUFFLE_STATUS_GROUP_SHUFFLE:
-                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+                    return PlaybackStateCompat.SHUFFLE_MODE_GROUP;
                 case JNI_SHUFFLE_STATUS_OFF:
-                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
-            }
-        }
-        return BluetoothAvrcpPlayerSettings.STATE_INVALID;
-    }
-
-    // Convert an AVRCP Setting/Value pair into the native equivalent value;
-    private static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
-        if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_EQUALIZER_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_ON:
-                    return JNI_EQUALIZER_STATUS_ON;
-            }
-        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_REPEAT_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
-                    return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT;
-                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
-                    return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT;
-                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
-                    return JNI_REPEAT_STATUS_GROUP_REPEAT;
-            }
-        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_SHUFFLE_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
-                    return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
-                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
-                    return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
-            }
-        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
-            switch (mSettingVal) {
-                case BluetoothAvrcpPlayerSettings.STATE_OFF:
-                    return JNI_SCAN_STATUS_OFF;
-                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
-                    return JNI_SCAN_STATUS_ALL_TRACK_SCAN;
-                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
-                    return JNI_SCAN_STATUS_GROUP_SCAN;
+                    return PlaybackStateCompat.SHUFFLE_MODE_NONE;
             }
         }
         return JNI_STATUS_INVALID;
     }
 
-    // convert a native Attribute Id into the AVRCP Setting equivalent value;
-    private static int mapBTAttribIdToAvrcpPlayerSettings(byte attribId) {
-        switch (attribId) {
-            case JNI_ATTRIB_EQUALIZER_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
-            case JNI_ATTRIB_REPEAT_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_REPEAT;
-            case JNI_ATTRIB_SHUFFLE_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE;
-            case JNI_ATTRIB_SCAN_STATUS:
-                return BluetoothAvrcpPlayerSettings.SETTING_SCAN;
-            default:
-                return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+    // Convert an AVRCP Setting/Value pair into the native equivalent value;
+    static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
+        if (mSetting == REPEAT_STATUS) {
+            switch (mSettingVal) {
+                case PlaybackStateCompat.REPEAT_MODE_NONE:
+                    return JNI_REPEAT_STATUS_OFF;
+                case PlaybackStateCompat.REPEAT_MODE_ONE:
+                    return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT;
+                case PlaybackStateCompat.REPEAT_MODE_ALL:
+                    return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT;
+                case PlaybackStateCompat.REPEAT_MODE_GROUP:
+                    return JNI_REPEAT_STATUS_GROUP_REPEAT;
+            }
+        } else if (mSetting == SHUFFLE_STATUS) {
+            switch (mSettingVal) {
+                case PlaybackStateCompat.SHUFFLE_MODE_NONE:
+                    return JNI_SHUFFLE_STATUS_OFF;
+                case PlaybackStateCompat.SHUFFLE_MODE_ALL:
+                    return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
+                case PlaybackStateCompat.SHUFFLE_MODE_GROUP:
+                    return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
+            }
         }
+        return JNI_STATUS_INVALID;
     }
-
 }
-
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 8bff73b..af28bf2 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -397,7 +397,7 @@
         // Android TV doesn't show consent dialogs for just works and encryption only le pairing
         boolean isAtvDevice = getApplicationContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK_ONLY);
-        initNative(isGuest(), isSingleUserMode(), isAtvDevice);
+        initNative(isGuest(), isNiapMode(), isAtvDevice);
         mNativeAvailable = true;
         mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
         mAppOps = getSystemService(AppOpsManager.class);
@@ -1563,7 +1563,6 @@
             if (service == null) {
                 return false;
             }
-            service.disable();
             return service.factoryReset();
 
         }
@@ -2877,8 +2876,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;
     }
 
     /**
@@ -2897,7 +2896,7 @@
 
     static native void classInitNative();
 
-    native boolean initNative(boolean startRestricted, boolean isSingleUserMode,
+    native boolean initNative(boolean startRestricted, boolean isNiapMode,
             boolean isAtvDevice);
 
     native void cleanupNative();
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 46e28a5..2965715 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -320,6 +320,7 @@
                     without waiting for the ACTION_UUID intent.
                     This was resulting in multiple calls to connect().*/
                     mUuids = null;
+                    mAlias = null;
                 }
             }
         }
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 80046d0..032780d 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -1987,10 +1987,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);
@@ -2015,6 +2020,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);
@@ -2052,7 +2064,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);
@@ -3188,6 +3201,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);
@@ -3198,7 +3227,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 c5485f9..e92688f 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -841,8 +841,6 @@
                     break;
                 }
                 case CALL_STATE_CHANGED: {
-                    if (mDeviceSilenced) break;
-
                     HeadsetCallState callState = (HeadsetCallState) message.obj;
                     if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
                         stateLogW("processCallState: failed to update call state " + callState);
@@ -1198,6 +1196,21 @@
         }
     }
 
+    class MyAudioServerStateCallback extends AudioManager.AudioServerStateCallback {
+        @Override
+        public void onAudioServerDown() {
+            logi("onAudioServerDown");
+        }
+
+        @Override
+        public void onAudioServerUp() {
+            logi("onAudioServerUp restoring audio parameters");
+            setAudioParameters();
+        }
+    }
+
+    MyAudioServerStateCallback mAudioServerStateCallback = new MyAudioServerStateCallback();
+
     class AudioOn extends ConnectedBase {
         @Override
         int getAudioStateInt() {
@@ -1216,10 +1229,21 @@
                 mHeadsetService.setActiveDevice(mDevice);
             }
             setAudioParameters();
+
+            mSystemInterface.getAudioManager().setAudioServerStateCallback(
+                    mHeadsetService.getMainExecutor(), mAudioServerStateCallback);
+
             broadcastStateTransitions();
         }
 
         @Override
+        public void exit() {
+            super.exit();
+
+            mSystemInterface.getAudioManager().clearAudioServerStateCallback();
+        }
+
+        @Override
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CONNECT: {
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
index 05af73e..b567371 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
@@ -224,7 +224,7 @@
         if (DBG) {
             Log.d(mTAG, "prevConn " + prevConn.isClosing() + " new call " + newCall.getState());
         }
-        if (prevConn.isClosing()
+        if (prevConn.isClosing() && prevConn.getCall().getState() != newCall.getState()
                 && newCall.getState() != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
             return true;
         }
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index 0a428b4..9b86aae 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -42,7 +42,6 @@
 
 import android.app.Activity;
 import android.app.PendingIntent;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothMapClient;
 import android.bluetooth.BluetoothProfile;
@@ -59,6 +58,7 @@
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.map.BluetoothMapbMessageMime;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
@@ -69,7 +69,6 @@
 
 import java.util.ArrayList;
 import java.util.Calendar;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -347,7 +346,9 @@
     void setDefaultMessageType(SdpMasRecord sdpMasRecord) {
         int supportedMessageTypes = sdpMasRecord.getSupportedMessageTypes();
         synchronized (mDefaultMessageType) {
-            if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) {
+            if ((supportedMessageTypes & SdpMasRecord.MessageType.MMS) > 0) {
+                mDefaultMessageType = Bmessage.Type.MMS;
+            } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) {
                 mDefaultMessageType = Bmessage.Type.SMS_CDMA;
             } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_GSM) > 0) {
                 mDefaultMessageType = Bmessage.Type.SMS_GSM;
@@ -473,6 +474,11 @@
                     }
                     break;
 
+                case MSG_MAS_DISCONNECTED:
+                    deferMessage(message);
+                    transitionTo(mDisconnecting);
+                    break;
+
                 case MSG_OUTBOUND_MESSAGE:
                     mMasClient.makeRequest(
                             new RequestPushMessage(FOLDER_OUTBOX, (Bmessage) message.obj, null,
@@ -625,9 +631,12 @@
             }
             ArrayList<com.android.bluetooth.mapclient.Message> messageListing = request.getList();
             if (messageListing != null) {
-                for (com.android.bluetooth.mapclient.Message msg : messageListing) {
+                // Message listings by spec arrive ordered newest first but we wish to broadcast as
+                // oldest first. Iterate in reverse order so we initiate requests oldest first.
+                for (int i = messageListing.size() - 1; i >= 0; i--) {
+                    com.android.bluetooth.mapclient.Message msg = messageListing.get(i);
                     if (DBG) {
-                        Log.d(TAG, "getting message ");
+                        Log.d(TAG, "getting message for handle " + msg.getHandle());
                     }
                     // A message listing coming from the server should always have up to date data
                     mMessages.put(msg.getHandle(), new MessageMetadata(msg.getHandle(),
@@ -665,6 +674,7 @@
             switch (message.getType()) {
                 case SMS_CDMA:
                 case SMS_GSM:
+                case MMS:
                     if (DBG) {
                         Log.d(TAG, "Body: " + message.getBodyContent());
                     }
@@ -705,6 +715,12 @@
                         intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME,
                                 originator.getDisplayName());
                     }
+                    if (message.getType() == Bmessage.Type.MMS) {
+                        BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime();
+                        mmsBmessage.parseMsgPart(message.getBodyContent());
+                        intent.putExtra(android.content.Intent.EXTRA_TEXT,
+                                mmsBmessage.getMessageAsText());
+                    }
                     // Only send to the current default SMS app if one exists
                     String defaultMessagingPackage = Telephony.Sms.getDefaultSmsPackage(mService);
                     if (defaultMessagingPackage != null) {
@@ -712,8 +728,6 @@
                     }
                     mService.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
                     break;
-
-                case MMS:
                 case EMAIL:
                 default:
                     Log.e(TAG, "Received unhandled type" + message.getType().toString());
diff --git a/src/com/android/bluetooth/mapclient/MnsObexServer.java b/src/com/android/bluetooth/mapclient/MnsObexServer.java
index 53cd79b..33ba1ea 100644
--- a/src/com/android/bluetooth/mapclient/MnsObexServer.java
+++ b/src/com/android/bluetooth/mapclient/MnsObexServer.java
@@ -90,6 +90,10 @@
         if (VDBG) {
             Log.v(TAG, "onDisconnect");
         }
+        MceStateMachine currentStateMachine = mStateMachineReference.get();
+        if (currentStateMachine != null) {
+            currentStateMachine.disconnect();
+        }
     }
 
     @Override
diff --git a/src/com/android/bluetooth/mapclient/MnsService.java b/src/com/android/bluetooth/mapclient/MnsService.java
index c1ab39e..b3317df 100644
--- a/src/com/android/bluetooth/mapclient/MnsService.java
+++ b/src/com/android/bluetooth/mapclient/MnsService.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.mapclient;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothServerSocket;
 import android.bluetooth.BluetoothSocket;
 import android.os.Handler;
@@ -129,6 +130,11 @@
                 Log.e(TAG, "Error: NO statemachine for device: " + device.getAddress()
                         + " (name: " + device.getName());
                 return false;
+            } else if (stateMachine.getState() != BluetoothProfile.STATE_CONNECTED) {
+                Log.e(TAG, "Error: statemachine for device: " + device.getAddress()
+                        + " (name: " + device.getName() + ") is not currently CONNECTED : "
+                        + stateMachine.getCurrentState());
+                return false;
             }
             MnsObexServer srv = new MnsObexServer(stateMachine, sServerSockets);
             BluetoothObexTransport transport = new BluetoothObexTransport(socket);
diff --git a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
index 2705e34..5b844dc 100644
--- a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
+++ b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
@@ -309,6 +309,12 @@
         String remng = mParser.remaining();
         byte[] data = remng.getBytes();
 
+        if (offset < 0 || offset > data.length) {
+            /* Handle possible exception for incorrect LENGTH value
+             * from MSE while parsing end of props */
+            throw new ParseException("Invalid LENGTH value", mParser.pos());
+        }
+
         /* restart parsing from after 'message'<CRLF> */
         mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos);
 
diff --git a/src/com/android/bluetooth/mapclient/obex/ObexTime.java b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
index 42a32c1..cc58a51 100644
--- a/src/com/android/bluetooth/mapclient/obex/ObexTime.java
+++ b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
@@ -29,8 +29,17 @@
 
     public ObexTime(String time) {
         /*
-         * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset
-         * +/-hhmm
+         * Match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset +/-hhmm
+         *
+         * Matched groups are numberes as follows:
+         *
+         *     YYYY MM DD T HH MM SS + hh mm
+         *     ^^^^ ^^ ^^   ^^ ^^ ^^ ^ ^^ ^^
+         *     1    2  3    4  5  6  8 9  10
+         *                          |---7---|
+         *
+         * All groups are guaranteed to be numeric so conversion will always succeed (except group 8
+         * which is either + or -)
          */
         Pattern p = Pattern.compile(
                 "(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2})" + ")?");
@@ -39,20 +48,26 @@
         if (m.matches()) {
 
             /*
-             * matched groups are numberes as follows: YYYY MM DD T HH MM SS +
-             * hh mm ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ 1 2 3 4 5 6 8 9 10 all groups
-             * are guaranteed to be numeric so conversion will always succeed
-             * (except group 8 which is either + or -)
+             * MAP spec says to default to "Local Time basis" for a message listing timestamp. We'll
+             * use the system default timezone and assume it knows best what our local timezone is.
+             * The builder defaults to the default locale and timezone if none is provided.
              */
+            Calendar.Builder builder = new Calendar.Builder();
 
-            Calendar cal = Calendar.getInstance();
-            cal.set(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1,
-                    Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)),
-                    Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6)));
+            /* Note that Calendar months are zero-based */
+            builder.setDate(Integer.parseInt(m.group(1)), /* year */
+                    Integer.parseInt(m.group(2)) - 1,     /* month */
+                    Integer.parseInt(m.group(3)));        /* day of month */
+
+            /* Note the MAP timestamp doesn't have milliseconds and we're explicitly setting to 0 */
+            builder.setTimeOfDay(Integer.parseInt(m.group(4)), /* hours */
+                    Integer.parseInt(m.group(5)),              /* minutes */
+                    Integer.parseInt(m.group(6)),              /* seconds */
+                    0);                                        /* milliseconds */
 
             /*
-             * if 7th group is matched then we have UTC offset information
-             * included
+             * If 7th group is matched then we're no longer using "Local Time basis" and instead
+             * have a UTC based timestamp and offset information included
              */
             if (m.group(7) != null) {
                 int ohh = Integer.parseInt(m.group(9));
@@ -68,10 +83,10 @@
                 TimeZone tz = TimeZone.getTimeZone("UTC");
                 tz.setRawOffset(offset);
 
-                cal.setTimeZone(tz);
+                builder.setTimeZone(tz);
             }
 
-            mDate = cal.getTime();
+            mDate = builder.build().getTime();
         }
     }
 
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index 8c52df7..92eab77 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -27,7 +27,6 @@
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -41,6 +40,7 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -68,6 +68,9 @@
     private String mNapIfaceAddr;
     private boolean mNativeAvailable;
 
+    @VisibleForTesting
+    UserManager mUserManager;
+
     private static final int MESSAGE_CONNECT = 1;
     private static final int MESSAGE_DISCONNECT = 2;
     private static final int MESSAGE_CONNECT_STATE_CHANGED = 11;
@@ -117,6 +120,8 @@
         initializeNative();
         mNativeAvailable = true;
 
+        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
+
         mNetworkFactory =
                 new BluetoothTetheringNetworkFactory(getBaseContext(), getMainLooper(), this);
         setPanService(this);
@@ -138,6 +143,9 @@
             cleanupNative();
             mNativeAvailable = false;
         }
+
+        mUserManager = null;
+
         if (mPanDevices != null) {
            int[] desiredStates = {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED,
                                   BluetoothProfile.STATE_DISCONNECTING};
@@ -287,13 +295,14 @@
         }
 
         @Override
-        public void setBluetoothTethering(boolean value) {
+        public void setBluetoothTethering(boolean value, String pkgName) {
             PanService service = getService();
             if (service == null) {
                 return;
             }
-            Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + service.mTetherOn);
-            service.setBluetoothTethering(value);
+            Log.d(TAG, "setBluetoothTethering: " + value + ", pkgName: " + pkgName
+                    + ", mTetherOn: " + service.mTetherOn);
+            service.setBluetoothTethering(value, pkgName);
         }
 
         @Override
@@ -319,6 +328,10 @@
 
     public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (mUserManager.isGuestUser()) {
+            Log.w(TAG, "Guest user does not have the permission to change the WiFi network");
+            return false;
+        }
         if (getConnectionState(device) != BluetoothProfile.STATE_DISCONNECTED) {
             Log.e(TAG, "Pan Device not disconnected: " + device);
             return false;
@@ -362,22 +375,14 @@
         return mTetherOn;
     }
 
-    void setBluetoothTethering(boolean value) {
+    void setBluetoothTethering(boolean value, final String pkgName) {
         if (DBG) {
             Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + mTetherOn);
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         final Context context = getBaseContext();
-        String pkgName = context.getOpPackageName();
 
-        // Clear caller identity temporarily so enforceTetherChangePermission UID checks work
-        // correctly
-        final long identityToken = Binder.clearCallingIdentity();
-        try {
-            ConnectivityManager.enforceTetherChangePermission(context, pkgName);
-        } finally {
-            Binder.restoreCallingIdentity(identityToken);
-        }
+        ConnectivityManager.enforceTetherChangePermission(context, pkgName);
 
         UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
         if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING) && value) {
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
index 34ab7d5..a5b3fbf 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -73,7 +73,7 @@
         oap.add(OAP_TAGID_FORMAT, format);
 
         /*
-         * maxListCount is a special case which is handled in
+         * maxListCount == 0 is a special case which is handled in
          * BluetoothPbapRequestPullPhoneBookSize
          */
         if (maxListCount > 0) {
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
new file mode 100644
index 0000000..f070661
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullPhoneBookSize extends BluetoothPbapRequest {
+
+    private static final boolean VDBG = Utils.VDBG;
+
+    private static final String TAG = "BtPbapReqPullPhoneBookSize";
+
+    private static final String TYPE = "x-bt/phonebook";
+
+    private int mSize;
+
+    BluetoothPbapRequestPullPhoneBookSize(String pbName, long filter) {
+        mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        // Set MaxListCount in the request to 0 to get PhonebookSize in the response.
+        // If a vCardSelector is present in the request, then the result shall
+        // contain the number of items that satisfy the selector’s criteria.
+        // See PBAP v1.2.3, Sec. 5.1.4.5.
+        oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+        if (filter != 0) {
+            oap.add(OAP_TAGID_FILTER, filter);
+        }
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        if (VDBG) {
+            Log.v(TAG, "readResponseHeaders");
+        }
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        if (oap.exists(OAP_TAGID_PHONEBOOK_SIZE)) {
+            mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+        }
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+}
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 6d14e59..4e4a240 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -32,8 +32,10 @@
 
 import com.android.bluetooth.BluetoothObexTransport;
 import com.android.bluetooth.R;
+import com.android.vcard.VCardEntry;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
 
 import javax.obex.ClientSession;
@@ -46,6 +48,15 @@
  * controlling state machine.
  */
 class PbapClientConnectionHandler extends Handler {
+    // Tradeoff: larger BATCH_SIZE leads to faster download rates, while smaller
+    // BATCH_SIZE is less prone to IO Exceptions if there is a download in
+    // progress when Bluetooth stack is torn down.
+    private static final int DEFAULT_BATCH_SIZE = 250;
+
+    // Upper limit on the indices of the vcf cards/entries, inclusive,
+    // i.e., valid indices are [0, 1, ... , UPPER_LIMIT]
+    private static final int UPPER_LIMIT = 65535;
+
     static final String TAG = "PbapClientConnHandler";
     static final boolean DBG = Utils.DBG;
     static final boolean VDBG = Utils.VDBG;
@@ -88,16 +99,26 @@
     private static final long PBAP_FILTER_NICKNAME = 1 << 23;
 
     private static final int PBAP_SUPPORTED_FEATURE =
-            PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING;
+            PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_DOWNLOADING;
     private static final long PBAP_REQUESTED_FIELDS =
             PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
                     | PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
     private static final int L2CAP_INVALID_PSM = -1;
 
     public static final String PB_PATH = "telecom/pb.vcf";
+    public static final String FAV_PATH = "telecom/fav.vcf";
     public static final String MCH_PATH = "telecom/mch.vcf";
     public static final String ICH_PATH = "telecom/ich.vcf";
     public static final String OCH_PATH = "telecom/och.vcf";
+    public static final String SIM_PB_PATH = "SIM1/telecom/pb.vcf";
+    public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf";
+    public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf";
+    public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf";
+
+    // PBAP v1.2.3 Sec. 7.1.2
+    private static final int SUPPORTED_REPOSITORIES_LOCALPHONEBOOK = 1 << 0;
+    private static final int SUPPORTED_REPOSITORIES_SIMCARD = 1 << 1;
+    private static final int SUPPORTED_REPOSITORIES_FAVORITES = 1 << 3;
 
     public static final int PBAP_V1_2 = 0x0102;
     public static final byte VCARD_TYPE_21 = 0;
@@ -239,29 +260,25 @@
                 break;
 
             case MSG_DOWNLOAD:
-                try {
-                    mAccountCreated = addAccount(mAccount);
-                    if (!mAccountCreated) {
-                        Log.e(TAG, "Account creation failed.");
-                        return;
-                    }
-                    // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2
-                    BluetoothPbapRequestPullPhoneBook request =
-                            new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount,
-                                    PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, 0, 1);
-                    request.execute(mObexSession);
-                    PhonebookPullRequest processor =
-                            new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
-                                    mAccount);
-                    processor.setResults(request.getList());
-                    processor.onPullComplete();
-                    HashMap<String, Integer> callCounter = new HashMap<>();
-                    downloadCallLog(MCH_PATH, callCounter);
-                    downloadCallLog(ICH_PATH, callCounter);
-                    downloadCallLog(OCH_PATH, callCounter);
-                } catch (IOException e) {
-                    Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString());
+                mAccountCreated = addAccount(mAccount);
+                if (!mAccountCreated) {
+                    Log.e(TAG, "Account creation failed.");
+                    return;
                 }
+                if (isRepositorySupported(SUPPORTED_REPOSITORIES_FAVORITES)) {
+                    downloadContacts(FAV_PATH);
+                }
+                if (isRepositorySupported(SUPPORTED_REPOSITORIES_LOCALPHONEBOOK)) {
+                    downloadContacts(PB_PATH);
+                }
+                if (isRepositorySupported(SUPPORTED_REPOSITORIES_SIMCARD)) {
+                    downloadContacts(SIM_PB_PATH);
+                }
+
+                HashMap<String, Integer> callCounter = new HashMap<>();
+                downloadCallLog(MCH_PATH, callCounter);
+                downloadCallLog(ICH_PATH, callCounter);
+                downloadCallLog(OCH_PATH, callCounter);
                 break;
 
             default:
@@ -337,7 +354,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();
         }
@@ -366,6 +386,59 @@
         }
     }
 
+    void downloadContacts(String path) {
+        try {
+            PhonebookPullRequest processor =
+                    new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
+                            mAccount);
+
+            // Download contacts in batches of size DEFAULT_BATCH_SIZE
+            BluetoothPbapRequestPullPhoneBookSize requestPbSize =
+                    new BluetoothPbapRequestPullPhoneBookSize(path,
+                            PBAP_REQUESTED_FIELDS);
+            requestPbSize.execute(mObexSession);
+
+            int numberOfContactsRemaining = requestPbSize.getSize();
+            int startOffset = 0;
+            if (PB_PATH.equals(path)) {
+                // PBAP v1.2.3, Sec 3.1.5. The first contact in pb is owner card 0.vcf, which we
+                // do not want to download. The other phonebook objects (e.g., fav) don't have an
+                // owner card, so they don't need an offset.
+                startOffset = 1;
+                // "-1" because Owner Card 0.vcf is also included in /pb, but not in /fav.
+                numberOfContactsRemaining -= 1;
+            }
+
+            while ((numberOfContactsRemaining > 0) && (startOffset <= UPPER_LIMIT)) {
+                int numberOfContactsToDownload =
+                        Math.min(Math.min(DEFAULT_BATCH_SIZE, numberOfContactsRemaining),
+                        UPPER_LIMIT - startOffset + 1);
+                BluetoothPbapRequestPullPhoneBook request =
+                        new BluetoothPbapRequestPullPhoneBook(path, mAccount,
+                                PBAP_REQUESTED_FIELDS, VCARD_TYPE_30,
+                                numberOfContactsToDownload, startOffset);
+                request.execute(mObexSession);
+                ArrayList<VCardEntry> vcards = request.getList();
+                if (path == FAV_PATH) {
+                    // mark each vcard as a favorite
+                    for (VCardEntry v : vcards) {
+                        v.setStarred(true);
+                    }
+                }
+                processor.setResults(vcards);
+                processor.onPullComplete();
+
+                startOffset += numberOfContactsToDownload;
+                numberOfContactsRemaining -= numberOfContactsToDownload;
+            }
+            if ((startOffset > UPPER_LIMIT) && (numberOfContactsRemaining > 0)) {
+                Log.w(TAG, "Download contacts incomplete, index exceeded upper limit.");
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Download contacts failure" + e.toString());
+        }
+    }
+
     void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
         try {
             BluetoothPbapRequestPullPhoneBook request =
@@ -416,4 +489,12 @@
             Log.d(TAG, "Call Logs could not be deleted, they may not exist yet.");
         }
     }
+
+    private boolean isRepositorySupported(int mask) {
+        if (mPseRec == null) {
+            if (VDBG) Log.v(TAG, "No PBAP Server SDP Record");
+            return false;
+        }
+        return (mask & mPseRec.getSupportedRepositories()) != 0;
+    }
 }
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index f150cdd..02b1e7a 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -19,9 +19,11 @@
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothPbapClient;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -31,6 +33,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
 import com.android.bluetooth.sdp.SdpManager;
 
 import java.util.ArrayList;
@@ -71,6 +74,9 @@
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         // delay initial download until after the user is unlocked to add an account.
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        // To remove call logs when PBAP was never connected while calls were made,
+        // we also listen for HFP to become disconnected.
+        filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
         try {
             registerReceiver(mPbapBroadcastReceiver, filter);
         } catch (Exception e) {
@@ -128,6 +134,21 @@
         }
     }
 
+    private void removeHfpCallLog(String accountName, Context context) {
+        if (DBG) Log.d(TAG, "Removing call logs from " + accountName);
+        // Delete call logs belonging to accountName==BD_ADDR that also match
+        // component name "hfpclient".
+        ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class);
+        String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND "
+                + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?";
+        String[] selectionArgs = new String[]{accountName, componentName.flattenToString()};
+        try {
+            getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
+        }
+    }
+
     private void registerSdpRecord() {
         SdpManager sdpManager = SdpManager.getDefaultManager();
         if (sdpManager == null) {
@@ -171,6 +192,21 @@
                 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
                     stateMachine.resumeDownload();
                 }
+            } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) {
+                // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects.
+                // However, if PBAP was never connected/enabled in the first place, and calls are
+                // made over HFP, these calllogs will not be removed when the device disconnects.
+                // This code ensures callogs are still removed in this case.
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+                if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+                    if (DBG) {
+                        Log.d(TAG, "Received intent to disconnect HFP with " + device);
+                    }
+                    // HFP client stores entries in calllog.db by BD_ADDR and component name
+                    removeHfpCallLog(device.getAddress(), context);
+                }
             }
         }
     }
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
index c121cf7..a97a5b0 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
@@ -23,11 +23,14 @@
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.content.res.Resources;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.bluetooth.R;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -40,75 +43,126 @@
 @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;
+    private A2dpCodecConfig mA2dpCodecConfig;
 
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
     @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;
+    private static final int[] sOptionalCodecTypes = new int[] {
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC
+    };
 
+    // Not use the default value to make sure it reads from config
+    private static final int SBC_PRIORITY_DEFAULT = 1001;
+    private static final int AAC_PRIORITY_DEFAULT = 3001;
+    private static final int APTX_PRIORITY_DEFAULT = 5001;
+    private static final int APTX_HD_PRIORITY_DEFAULT = 7001;
+    private static final int LDAC_PRIORITY_DEFAULT = 9001;
+    private static final int PRIORITY_HIGH = 1000000;
+
+    private static final BluetoothCodecConfig[] sCodecCapabilities = new BluetoothCodecConfig[] {
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     SBC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_MONO
+                                     | BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            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
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                     APTX_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                     APTX_HD_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                     LDAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000
+                                     | BluetoothCodecConfig.SAMPLE_RATE_88200
+                                     | BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16
+                                     | BluetoothCodecConfig.BITS_PER_SAMPLE_24
+                                     | BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0)        // Codec-specific fields
+    };
+
+    private static final BluetoothCodecConfig[] sDefaultCodecConfigs = new BluetoothCodecConfig[] {
+            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
+            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
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                     APTX_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                     APTX_HD_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                     LDAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0)        // Codec-specific fields
+    };
 
     @Before
     public void setUp() throws Exception {
-        mTargetContext = InstrumentationRegistry.getTargetContext();
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
+        mTargetContext = InstrumentationRegistry.getTargetContext();
 
-        mA2dpCodecConfig = new A2dpCodecConfig(mTargetContext, mA2dpNativeInterface);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_sbc))
+                .thenReturn(SBC_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aac))
+                .thenReturn(AAC_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx))
+                .thenReturn(APTX_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd))
+                .thenReturn(APTX_HD_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_ldac))
+                .thenReturn(LDAC_PRIORITY_DEFAULT);
+
+        mA2dpCodecConfig = new A2dpCodecConfig(mMockContext, 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
@@ -139,128 +193,383 @@
         }
     }
 
+    /**
+     * Test that we can fallback to default codec by lower the codec priority we changed before.
+     */
     @Test
     public void testSetCodecPreference_priorityHighToDefault() {
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, SBC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, AAC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, APTX_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, APTX_HD_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
                 false);
     }
 
+    /**
+     * Test that we can change the default codec to another by raising the codec priority.
+     * LDAC is the default highest codec, so no need to test others.
+     */
     @Test
-    public void testSetCodecPreference_priorityDefaultToHigh() {
-        testSetCodecPreference_codecPriorityChangeCase(
+    public void testSetCodecPreference_priorityDefaultToRaiseHigh() {
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 false);
     }
 
     @Test
-    public void testSetCodecPreference_priorityHighToHigh() {
-        testSetCodecPreference_codecPriorityChangeCase(
+    public void testSetCodecPreference_prioritySbcHighToOthersHigh() {
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 false);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
     }
 
     @Test
-    public void testSetCodecPreference_parametersChange() {
-        int unSupportedParameter = 200;
+    public void testSetCodecPreference_priorityAacHighToOthersHigh() {
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                false);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+    }
 
-        testSetCodecPreference_parametersChangeCase(
+    @Test
+    public void testSetCodecPreference_parametersChangedInSameCodec() {
+        // The SBC default / preferred config is Stereo
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 BluetoothCodecConfig.SAMPLE_RATE_44100,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
-        testSetCodecPreference_parametersChangeCase(
+        // SBC Mono is mandatory
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 BluetoothCodecConfig.SAMPLE_RATE_44100,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_MONO,
                 true);
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_44100,
-                unSupportedParameter,
+
+        // The LDAC default / preferred config within mDefaultCodecConfigs
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
 
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_48000,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 true);
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_48000,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 true);
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_48000,
-                unSupportedParameter,
+
+        // None for system default (Developer options)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
                 false);
 
-        testSetCodecPreference_parametersChangeCase(
-                unSupportedParameter,
+        int unsupportedParameter = 0xc0;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                unsupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                unsupportedParameter,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                unsupportedParameter,
+                false);
+
+        int multipleSupportedParameters = 0x03;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                multipleSupportedParameters,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_parametersChangedToAnotherCodec() {
+        // different sample rate (44.1 kHz -> 96 kHz)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        // different bits per channel (16 bits -> 32 bits)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        // change all PCM parameters
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                true);
+
+        // None for system default (Developer options)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                true);
+
+        int unsupportedParameter = 0xc0;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                unsupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
-        testSetCodecPreference_parametersChangeCase(
-                unSupportedParameter,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                unsupportedParameter,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
-        testSetCodecPreference_parametersChangeCase(
-                unSupportedParameter,
-                unSupportedParameter,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                unsupportedParameter,
+                false);
+
+        int multipleSupportedParameters = 0x03;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                multipleSupportedParameters,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_ldacCodecSpecificFieldChanged() {
+        int ldacAudioQualityHigh = 1000;
+        int ldacAudioQualityABR = 1003;
+        int sbcCodecSpecificParameter = 0;
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                false);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityHigh,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
+                true);
+
+        // Only LDAC will check the codec specific1 field, but not SBC
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                ldacAudioQualityHigh,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
                 false);
     }
 
     @Test
     public void testDisableOptionalCodecs() {
-        int verifyCount = 0;
-        BluetoothCodecConfig[] codecConfigArray =
+        BluetoothCodecConfig[] codecConfigsArray =
                 new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
-        codecConfigArray[0] = new BluetoothCodecConfig(
+        codecConfigsArray[0] = new BluetoothCodecConfig(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
                 BluetoothCodecConfig.SAMPLE_RATE_NONE,
@@ -268,32 +577,32 @@
                 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);
+        // shouldn't invoke to native when current codec is SBC
+        mA2dpCodecConfig.disableOptionalCodecs(
+                mTestDevice,
+                getDefaultCodecConfigByType(
+                        BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                        BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+        verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+                                                                        codecConfigsArray);
 
-        // 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);
+        // should invoke to native when current codec is an optional codec
+        int invokedCounter = 0;
+        for (int codecType : sOptionalCodecTypes) {
+            mA2dpCodecConfig.disableOptionalCodecs(
+                    mTestDevice,
+                    getDefaultCodecConfigByType(codecType,
+                                                BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+            verify(mA2dpNativeInterface, times(++invokedCounter))
+                    .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+        }
     }
 
     @Test
     public void testEnableOptionalCodecs() {
-        int verifyCount = 0;
-        BluetoothCodecConfig[] codecConfigArray =
+        BluetoothCodecConfig[] codecConfigsArray =
                 new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
-        codecConfigArray[0] = new BluetoothCodecConfig(
+        codecConfigsArray[0] = new BluetoothCodecConfig(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 SBC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SAMPLE_RATE_NONE,
@@ -301,115 +610,292 @@
                 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);
+        // should invoke to native when current codec is SBC
+        mA2dpCodecConfig.enableOptionalCodecs(
+                mTestDevice,
+                getDefaultCodecConfigByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+        verify(mA2dpNativeInterface, times(1))
+                .setCodecConfigPreference(mTestDevice, codecConfigsArray);
 
-        // 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);
+        // shouldn't invoke to native when current codec is already an optional
+        for (int codecType : sOptionalCodecTypes) {
+            mA2dpCodecConfig.enableOptionalCodecs(
+                    mTestDevice,
+                    getDefaultCodecConfigByType(codecType,
+                                                BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+            verify(mA2dpNativeInterface, times(1))
+                    .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+        }
     }
 
-    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
+    private BluetoothCodecConfig getDefaultCodecConfigByType(int codecType, int codecPriority) {
+        for (BluetoothCodecConfig codecConfig : sDefaultCodecConfigs) {
+            if (codecConfig.getCodecType() != codecType) {
+                continue;
+            }
+            return new BluetoothCodecConfig(
+                    codecConfig.getCodecType(),
+                    (codecPriority != BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT
+                     ? codecPriority : codecConfig.getCodecPriority()),
+                    codecConfig.getSampleRate(), codecConfig.getBitsPerSample(),
+                    codecConfig.getChannelMode(), codecConfig.getCodecSpecific1(),
+                    codecConfig.getCodecSpecific2(), codecConfig.getCodecSpecific3(),
+                    codecConfig.getCodecSpecific4());
+        }
+        Assert.fail("getDefaultCodecConfigByType: No such codecType=" + codecType
+                + " in sDefaultCodecConfigs");
+        return null;
+    }
+
+    private BluetoothCodecConfig getCodecCapabilitiesByType(int codecType) {
+        for (BluetoothCodecConfig codecCapabilities : sCodecCapabilities) {
+            if (codecCapabilities.getCodecType() != codecType) {
+                continue;
+            }
+            return new BluetoothCodecConfig(
+                    codecCapabilities.getCodecType(), codecCapabilities.getCodecPriority(),
+                    codecCapabilities.getSampleRate(), codecCapabilities.getBitsPerSample(),
+                    codecCapabilities.getChannelMode(), codecCapabilities.getCodecSpecific1(),
+                    codecCapabilities.getCodecSpecific2(), codecCapabilities.getCodecSpecific3(),
+                    codecCapabilities.getCodecSpecific4());
+        }
+        Assert.fail("getCodecCapabilitiesByType: No such codecType=" + codecType
+                + " in sCodecCapabilities");
+        return null;
+    }
+
+    private void testCodecParametersChangeHelper(int newCodecType, int oldCodecType,
+            int sampleRate, int bitsPerSample, int channelMode, boolean invokeNative) {
+        BluetoothCodecConfig oldCodecConfig =
+                getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+                new BluetoothCodecConfig(newCodecType,
+                                         PRIORITY_HIGH,
+                                         sampleRate, bitsPerSample, channelMode,
+                                         0, 0, 0, 0)       // Codec-specific fields
+        };
+
+        // Test cases: 1. no mandatory; 2. mandatory + old + new; 3. all codecs
+        BluetoothCodecConfig[] minimumCodecsArray;
+        if (!oldCodecConfig.isMandatoryCodec() && !newCodecConfigsArray[0].isMandatoryCodec()) {
+            BluetoothCodecConfig[] optionalCodecsArray;
+            if (oldCodecType != newCodecType) {
+                optionalCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+            } else {
+                optionalCodecsArray = new BluetoothCodecConfig[]
+                        {getCodecCapabilitiesByType(oldCodecType)};
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+            }
+            BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                        sCodecCapabilities,
+                                                                        optionalCodecsArray);
+            mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                      codecStatus,
+                                                      newCodecConfigsArray[0]);
+            // no mandatory codec in selectable, and should not apply
+            verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+                                                                            newCodecConfigsArray);
 
 
-        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
+        } else {
+            if (oldCodecType != newCodecType) {
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+            } else {
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                };
+            }
+        }
 
-        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]);
+        // 2. mandatory + old + new codecs only
+        BluetoothCodecStatus codecStatus =
+                new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
         verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
 
-    }
-
-    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]);
-
+        // 3. all codecs were selectable
         codecStatus = new BluetoothCodecStatus(oldCodecConfig,
-                                               selectableCodecs,
-                                               selectableCodecs);
+                                               sCodecCapabilities,
+                                               sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokeNative ? 2 : 0))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+    }
 
-        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+    private void testCodecSpecificParametersChangeHelper(int newCodecType, int newCodecSpecific,
+            int oldCodecType, int oldCodecSpecific, boolean invokeNative) {
+        BluetoothCodecConfig codecDefaultTemp =
+                getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig oldCodecConfig =
+                new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+                                         codecDefaultTemp.getCodecPriority(),
+                                         codecDefaultTemp.getSampleRate(),
+                                         codecDefaultTemp.getBitsPerSample(),
+                                         codecDefaultTemp.getChannelMode(),
+                                         oldCodecSpecific, 0, 0, 0);       // Codec-specific fields
+        codecDefaultTemp = getDefaultCodecConfigByType(newCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+                new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+                                         codecDefaultTemp.getCodecPriority(),
+                                         codecDefaultTemp.getSampleRate(),
+                                         codecDefaultTemp.getBitsPerSample(),
+                                         codecDefaultTemp.getChannelMode(),
+                                         newCodecSpecific, 0, 0, 0)       // Codec-specific fields
+        };
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                    sCodecCapabilities,
+                                                                    sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
         verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+    }
+
+    private void testCodecPriorityChangeHelper(int newCodecType, int newCodecPriority,
+            int oldCodecType, int oldCodecPriority, boolean shouldApplyWhenAllSelectable) {
+
+        BluetoothCodecConfig[] newCodecConfigsArray =
+                new BluetoothCodecConfig[] {
+                        getDefaultCodecConfigByType(newCodecType, newCodecPriority)
+                };
+        BluetoothCodecConfig oldCodecConfig = getDefaultCodecConfigByType(oldCodecType,
+                                                                          oldCodecPriority);
+
+        // Test cases: 1. no mandatory; 2. no new codec; 3. mandatory + old + new; 4. all codecs
+        BluetoothCodecConfig[] minimumCodecsArray;
+        boolean isMinimumCodecsArraySelectable;
+        if (!oldCodecConfig.isMandatoryCodec()) {
+            if (oldCodecType == newCodecType || newCodecConfigsArray[0].isMandatoryCodec()) {
+                // selectable: {-mandatory, +oldCodec = newCodec}, or
+                // selectable: {-mandatory = newCodec, +oldCodec}. Not applied
+                BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+                        {getCodecCapabilitiesByType(oldCodecType)};
+                BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                            sCodecCapabilities,
+                                                                            poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                          codecStatus,
+                                                          newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec = newCodec}, or
+                // selectable: {+mandatory = newCodec, +oldCodec}.
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+            } else {
+                // selectable: {-mandatory, +oldCodec, +newCodec}. Not applied
+                BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+                BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                            sCodecCapabilities,
+                                                                            poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(
+                        mTestDevice, codecStatus, newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec, -newCodec}. Not applied
+                poorCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+                codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                       sCodecCapabilities,
+                                                       poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                          codecStatus,
+                                                          newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec, +newCodec}.
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                      getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                      getCodecCapabilitiesByType(oldCodecType),
+                      getCodecCapabilitiesByType(newCodecType)
+                };
+            }
+            // oldCodec priority should be reset to default, so compare with the default
+            if (newCodecConfigsArray[0].getCodecPriority()
+                    > getDefaultCodecConfigByType(
+                            oldCodecType,
+                            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT).getCodecPriority()
+                    && oldCodecType != newCodecType) {
+                isMinimumCodecsArraySelectable = true;
+            } else {
+                // the old codec was still the highest priority after reset to default
+                isMinimumCodecsArraySelectable = false;
+            }
+        } else if (oldCodecType != newCodecType) {
+            // selectable: {+mandatory = oldCodec, -newCodec}. Not applied
+            BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+                    {getCodecCapabilitiesByType(oldCodecType)};
+            BluetoothCodecStatus codecStatus =
+                    new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, poorCodecsArray);
+            mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                      codecStatus,
+                                                      newCodecConfigsArray[0]);
+            verify(mA2dpNativeInterface, times(0))
+                    .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+            // selectable: {+mandatory = oldCodec, +newCodec}.
+            minimumCodecsArray = new BluetoothCodecConfig[] {
+                    getCodecCapabilitiesByType(oldCodecType),
+                    getCodecCapabilitiesByType(newCodecType)
+            };
+            isMinimumCodecsArraySelectable = true;
+        } else {
+            // selectable: {mandatory = oldCodec = newCodec}.
+            minimumCodecsArray = new BluetoothCodecConfig[]
+                    {getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC)};
+            isMinimumCodecsArraySelectable = false;
+        }
+
+        // 3. mandatory + old + new codecs only
+        int invokedCounter = (isMinimumCodecsArraySelectable ? 1 : 0);
+        BluetoothCodecStatus codecStatus =
+                new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokedCounter))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+        // 4. all codecs were selectable
+        invokedCounter += (shouldApplyWhenAllSelectable ? 1 : 0);
+        codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                               sCodecCapabilities,
+                                               sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokedCounter))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
     }
 }
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index 53e8419..b8fbbb9 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -195,6 +195,39 @@
     }
 
     @Test
+    public void testFocusRerequest() {
+        // Focus was lost transiently, expect streaming to stop.
+        testSnkPlay();
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
+        verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
+        verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS, true));
+        verify(mMockAudioManager, times(2)).requestAudioFocus(any());
+    }
+
+    @Test
+    public void testFocusGainTransient() {
+        // Focus was lost then regained.
+        testSnkPlay();
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.DELAYED_PAUSE));
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+                        AudioManager.AUDIOFOCUS_GAIN));
+        verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
+        verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
+        verify(mMockA2dpSink, times(2)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
     public void testFocusLost() {
         // Focus was lost permanently, expect streaming to stop.
         testSnkPlay();
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
index b1d1743..907f0dc 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -23,8 +23,11 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
-import android.media.session.MediaController;
+import android.content.res.Resources;
+import android.media.AudioManager;
 import android.os.Looper;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
@@ -33,6 +36,7 @@
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
 
@@ -65,13 +69,23 @@
     private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
     private byte[] mTestAddress = new byte[]{00, 01, 02, 03, 04, 05};
 
-    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+    @Rule public final ServiceTestRule mAvrcpServiceRule = new ServiceTestRule();
+    @Rule public final ServiceTestRule mA2dpServiceRule = new ServiceTestRule();
 
     @Mock
-    private AdapterService mAdapterService;
+    private AdapterService mAvrcpAdapterService;
+
+    @Mock
+    private AdapterService mA2dpAdapterService;
+
+    @Mock
+    private AudioManager mAudioManager;
     @Mock
     private AvrcpControllerService mAvrcpControllerService;
 
+    @Mock
+    private Resources mMockResources;
+
     AvrcpControllerStateMachine mAvrcpStateMachine;
 
     @Before
@@ -87,9 +101,19 @@
 
         // Setup mocks and test assets
         MockitoAnnotations.initMocks(this);
-        TestUtils.setAdapterService(mAdapterService);
-        TestUtils.startService(mServiceRule, AvrcpControllerService.class);
-        doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources();
+        TestUtils.setAdapterService(mAvrcpAdapterService);
+        TestUtils.startService(mAvrcpServiceRule, AvrcpControllerService.class);
+        TestUtils.clearAdapterService(mAvrcpAdapterService);
+        TestUtils.setAdapterService(mA2dpAdapterService);
+        TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class);
+        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
+                .thenReturn(true);
+        doReturn(mMockResources).when(mAvrcpControllerService).getResources();
+        doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
+        doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
+        doReturn(true).when(mAudioManager).isVolumeFixed();
+        doReturn(mAudioManager).when(mAvrcpControllerService)
+                .getSystemService(Context.AUDIO_SERVICE);
 
         // This line must be called to make sure relevant objects are initialized properly
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -105,7 +129,7 @@
         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) {
             return;
         }
-        TestUtils.clearAdapterService(mAdapterService);
+        TestUtils.clearAdapterService(mA2dpAdapterService);
     }
 
     /**
@@ -133,6 +157,10 @@
                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+        Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
+                BluetoothMediaBrowserService.getPlaybackState());
     }
 
     /**
@@ -141,6 +169,11 @@
     @Test
     public void testControlOnly() {
         int numBroadcastsSent = setUpConnectedState(true, false);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+        Assert.assertNotNull(transportControls);
+        Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
+                BluetoothMediaBrowserService.getPlaybackState());
         StackEvent event =
                 StackEvent.connectionStateChanged(false, false);
         mAvrcpStateMachine.disconnect();
@@ -158,6 +191,8 @@
                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+        Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
+                BluetoothMediaBrowserService.getPlaybackState());
     }
 
     /**
@@ -168,6 +203,8 @@
         Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
         int numBroadcastsSent = setUpConnectedState(false, true);
         Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
+        Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
+                BluetoothMediaBrowserService.getPlaybackState());
         StackEvent event =
                 StackEvent.connectionStateChanged(false, false);
         mAvrcpStateMachine.disconnect();
@@ -185,6 +222,10 @@
                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+        Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
+                BluetoothMediaBrowserService.getPlaybackState());
     }
 
     /**
@@ -213,7 +254,7 @@
     @Test
     public void testPlay() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Play
@@ -232,7 +273,7 @@
     @Test
     public void testPause() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Pause
@@ -251,7 +292,7 @@
     @Test
     public void testStop() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Stop
@@ -270,7 +311,7 @@
     @Test
     public void testNext() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Next
@@ -290,7 +331,7 @@
     @Test
     public void testPrevious() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Previous
@@ -310,7 +351,7 @@
     @Test
     public void testFastForward() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //FastForward
@@ -331,7 +372,7 @@
     @Test
     public void testRewind() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Rewind
@@ -347,6 +388,69 @@
     }
 
     /**
+     * Test media browser skip to queue item
+     */
+    @Test
+    public void testSkipToQueueInvalid() throws Exception {
+        byte scope = 1;
+        int minSize = 0;
+        int maxSize = 255;
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Play an invalid item below start
+        transportControls.skipToQueueItem(minSize - 1);
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative(
+                eq(mTestAddress), eq(scope), anyLong(), anyInt());
+
+        //Play an invalid item beyond end
+        transportControls.skipToQueueItem(maxSize + 1);
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative(
+                eq(mTestAddress), eq(scope), anyLong(), anyInt());
+    }
+
+    /**
+     * Test media browser shuffle command
+     */
+    @Test
+    public void testShuffle() throws Exception {
+        byte[] shuffleSetting = new byte[]{3};
+        byte[] shuffleMode = new byte[]{2};
+
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Shuffle
+        transportControls.setShuffleMode(1);
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
+                .setPlayerApplicationSettingValuesNative(
+                eq(mTestAddress), eq((byte) 1), eq(shuffleSetting), eq(shuffleMode));
+    }
+
+    /**
+     * Test media browser repeat command
+     */
+    @Test
+    public void testRepeat() throws Exception {
+        byte[] repeatSetting = new byte[]{2};
+        byte[] repeatMode = new byte[]{3};
+
+        setUpConnectedState(true, true);
+        MediaControllerCompat.TransportControls transportControls =
+                BluetoothMediaBrowserService.getTransportControls();
+
+        //Shuffle
+        transportControls.setRepeatMode(2);
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
+                .setPlayerApplicationSettingValuesNative(
+                eq(mTestAddress), eq((byte) 1), eq(repeatSetting), eq(repeatMode));
+    }
+
+    /**
      * Test media browsing
      * Verify that a browse tree is created with the proper root
      * Verify that a player can be fetched and added to the browse tree
@@ -475,7 +579,7 @@
         BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
         mAvrcpStateMachine.requestContents(results);
 
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
         transportControls.play();
         verify(mAvrcpControllerService,
@@ -487,6 +591,57 @@
     }
 
     /**
+     * Test that Absolute Volume Registration is working
+     */
+    @Test
+    public void testRegisterAbsVolumeNotification() {
+        setUpConnectedState(true, true);
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
+                .sendRegisterAbsVolRspNative(any(), anyByte(), eq(127), anyInt());
+    }
+
+    /**
+     * Test playback does not request focus when another app is playing music.
+     */
+    @Test
+    public void testPlaybackWhileMusicPlaying() {
+        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
+                .thenReturn(false);
+        Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
+        doReturn(true).when(mAudioManager).isMusicActive();
+        setUpConnectedState(true, true);
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
+                PlaybackStateCompat.STATE_PLAYING);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
+        TestUtils.waitForLooperToFinishScheduledTask(
+                A2dpSinkService.getA2dpSinkService().getMainLooper());
+        Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
+    }
+
+    /**
+     * Test playback requests focus while nothing is playing music.
+     */
+    @Test
+    public void testPlaybackWhileIdle() {
+        Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
+        doReturn(false).when(mAudioManager).isMusicActive();
+        setUpConnectedState(true, true);
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
+                PlaybackStateCompat.STATE_PLAYING);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        TestUtils.waitForLooperToFinishScheduledTask(
+                A2dpSinkService.getA2dpSinkService().getMainLooper());
+        Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState());
+    }
+
+    /**
      * Setup Connected State
      *
      * @return number of times mAvrcpControllerService.sendBroadcastAsUser() has been invoked
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 2c8c9e2..28866aa 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 */,
                 false);
         adapterService.cleanupNative();
         HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig();
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
index 5ddf14f..396bb5d 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 */,
                 false);
 
         TestUtils.setAdapterService(mMockAdapterService);
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java b/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java
new file mode 100644
index 0000000..acd05ed
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static org.mockito.Mockito.*;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BmessageTest {
+    private static final String TAG = BmessageTest.class.getSimpleName();
+    private static final String SIMPLE_MMS_MESSAGE =
+            "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n"
+            + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n"
+            + "BEGIN:BBODY\r\nLENGTH:39\r\nBEGIN:MSG\r\nThis is a new msg\r\nEND:MSG\r\n"
+            + "END:BBODY\r\nEND:BENV\r\nEND:BMSG\r\n";
+
+    private static final String NO_END_MESSAGE =
+            "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n"
+            + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n"
+            + "BEGIN:BBODY\r\nLENGTH:39\r\nBEGIN:MSG\r\nThis is a new msg\r\n";
+
+    private static final String WRONG_LENGTH_MESSAGE =
+            "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n"
+            + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n"
+            + "BEGIN:BBODY\r\nLENGTH:200\r\nBEGIN:MSG\r\nThis is a new msg\r\nEND:MSG\r\n"
+            + "END:BBODY\r\nEND:BENV\r\nEND:BMSG\r\n";
+
+    private static final String NO_BODY_MESSAGE =
+            "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n"
+            + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n"
+            + "BEGIN:BBODY\r\nLENGTH:\r\n";
+
+    private static final String NEGATIVE_LENGTH_MESSAGE =
+            "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n"
+            + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n"
+            + "BEGIN:BBODY\r\nLENGTH:-1\r\nBEGIN:MSG\r\nThis is a new msg\r\nEND:MSG\r\n"
+            + "END:BBODY\r\nEND:BENV\r\nEND:BMSG\r\n";
+
+    @Test
+    public void testNormalMessages() {
+        Bmessage message = BmessageParser.createBmessage(SIMPLE_MMS_MESSAGE);
+        Assert.assertNotNull(message);
+    }
+
+    @Test
+    public void testParseWrongLengthMessage() {
+        Bmessage message = BmessageParser.createBmessage(WRONG_LENGTH_MESSAGE);
+        Assert.assertNull(message);
+    }
+
+    @Test
+    public void testParseNoEndMessage() {
+        Bmessage message = BmessageParser.createBmessage(NO_END_MESSAGE);
+        Assert.assertNull(message);
+    }
+
+    @Test
+    public void testParseReallyLongMessage() {
+        String testMessage = new String(new char[68048]).replace('\0', 'A');
+        Bmessage message = BmessageParser.createBmessage(testMessage);
+        Assert.assertNull(message);
+    }
+
+    @Test
+    public void testNoBodyMessage() {
+        Bmessage message = BmessageParser.createBmessage(NO_BODY_MESSAGE);
+        Assert.assertNull(message);
+    }
+
+    @Test
+    public void testNegativeLengthMessage() {
+        Bmessage message = BmessageParser.createBmessage(NEGATIVE_LENGTH_MESSAGE);
+        Assert.assertNull(message);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
index 70854d8..ccfd9f8 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
@@ -150,6 +150,36 @@
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
     }
 
+     /**
+     * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED
+     * --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
+     */
+    @Test
+    public void testStateTransitionFromConnectedWithMasDisconnected() {
+        Log.i(TAG, "in testStateTransitionFromConnectedWithMasDisconnected");
+
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.sendMessage(msg);
+
+        // Wait until the message is processed and a broadcast request is sent to
+        // to MapClientService to change
+        // state from STATE_CONNECTING to STATE_CONNECTED
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+
+        msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
+        mMceStateMachine.sendMessage(msg);
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast(
+                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
+    }
+
+
     /**
      * Test receiving an empty event report
      */
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java b/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java
new file mode 100644
index 0000000..5ef2d45
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+@RunWith(AndroidJUnit4.class)
+public class ObexTimeTest {
+    private static final String TAG = ObexTimeTest.class.getSimpleName();
+
+    private static final String VALID_TIME_STRING = "20190101T121314";
+    private static final String VALID_TIME_STRING_WITH_OFFSET_POS = "20190101T121314+0130";
+    private static final String VALID_TIME_STRING_WITH_OFFSET_NEG = "20190101T121314-0130";
+
+    private static final String INVALID_TIME_STRING_OFFSET_EXTRA_DIGITS = "20190101T121314-99999";
+    private static final String INVALID_TIME_STRING_BAD_DELIMITER = "20190101Q121314";
+
+    // MAP message listing times, per spec, use "local time basis" if UTC offset isn't given. The
+    // ObexTime class parses using the current default timezone (assumed to be the "local timezone")
+    // in the case that UTC isn't provided. However, the Date class assumes UTC ALWAYS when
+    // initializing off of a long value. We have to take that into account when determining our
+    // expected results for time strings that don't have an offset.
+    private static final long LOCAL_TIMEZONE_OFFSET = TimeZone.getDefault().getRawOffset();
+
+    // If you are a positive offset from GMT then GMT is in the "past" and you need to subtract that
+    // offset from the time. If you are negative then GMT is in the future and you need to add that
+    // offset to the time.
+    private static final long VALID_TS = 1546344794000L; // Jan 01, 2019 at 12:13:14 GMT
+    private static final long TS_OFFSET = 5400000L; // 1 Hour, 30 minutes -> milliseconds
+    private static final long VALID_TS_LOCAL_TZ = VALID_TS - LOCAL_TIMEZONE_OFFSET;
+    private static final long VALID_TS_OFFSET_POS = VALID_TS - TS_OFFSET;
+    private static final long VALID_TS_OFFSET_NEG = VALID_TS + TS_OFFSET;
+
+    private static final Date VALID_DATE_LOCAL_TZ = new Date(VALID_TS_LOCAL_TZ);
+    private static final Date VALID_DATE_WITH_OFFSET_POS = new Date(VALID_TS_OFFSET_POS);
+    private static final Date VALID_DATE_WITH_OFFSET_NEG = new Date(VALID_TS_OFFSET_NEG);
+
+    @Test
+    public void createWithValidDateTimeString_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING);
+        Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_LOCAL_TZ,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithValidDateTimeStringWithPosOffset_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_POS);
+        Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_WITH_OFFSET_POS,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithValidDateTimeStringWithNegOffset_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_NEG);
+        Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_WITH_OFFSET_NEG,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithValidDate_TimestampCorrect() {
+        ObexTime timestamp = new ObexTime(VALID_DATE_LOCAL_TZ);
+        Assert.assertEquals("ObexTime created with a date must return the same date",
+                VALID_DATE_LOCAL_TZ, timestamp.getTime());
+    }
+
+    @Test
+    public void printValidTime_TimestampMatchesInput() {
+        ObexTime timestamp = new ObexTime(VALID_TIME_STRING);
+        Assert.assertEquals("Timestamp as a string must match the input string", VALID_TIME_STRING,
+                timestamp.toString());
+    }
+
+    @Test
+    public void createWithInvalidDelimiterString_TimestampIsNull() {
+        ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER);
+        Assert.assertEquals("Parsed timestamp was invalid and must result in a null object", null,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void createWithInvalidOffsetString_TimestampIsNull() {
+        ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_OFFSET_EXTRA_DIGITS);
+        Assert.assertEquals("Parsed timestamp was invalid and must result in a null object", null,
+                timestamp.getTime());
+    }
+
+    @Test
+    public void printInvalidTime_ReturnsNull() {
+        ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER);
+        Assert.assertEquals("Invalid timestamps must return null for toString()", null,
+                timestamp.toString());
+    }
+}
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
diff --git a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
index 6d4574f..3573f84 100644
--- a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
@@ -15,8 +15,12 @@
  */
 package com.android.bluetooth.pan;
 
+import static org.mockito.Mockito.when;
+
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.os.UserManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
@@ -47,6 +51,7 @@
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
     @Mock private AdapterService mAdapterService;
+    @Mock private UserManager mMockUserManager;
 
     @Before
     public void setUp() throws Exception {
@@ -61,6 +66,7 @@
         // Try getting the Bluetooth adapter
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         Assert.assertNotNull(mAdapter);
+        mService.mUserManager = mMockUserManager;
     }
 
     @After
@@ -78,4 +84,11 @@
     public void testInitialize() {
         Assert.assertNotNull(PanService.getPanService());
     }
+
+    @Test
+    public void testGuestUserConnect() {
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        when(mMockUserManager.isGuestUser()).thenReturn(true);
+        Assert.assertFalse(mService.connect(device));
+    }
 }