Merge changes I305b3231,I2fcd959a,I31dec95f into qt-qpr1-dev

* changes:
  Delete call logs when calls are made without PBAP
  Set Browsing bit to 0 in PbapSupportedFeatures
  Prevent phone's bd_addr from appearing in Accounts
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/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 1525a71..a825010 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>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a9e448c..7d371e0 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>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index f4b7e7a..c9b6335 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>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 0ed4aa9..a0fa232 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>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 08e907f..5198b87 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>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 4a40dfe..c60c8de 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -128,7 +128,7 @@
     <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>
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/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/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/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index 5e3b356..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;
@@ -24,10 +25,9 @@
 import android.media.AudioManager;
 import android.media.AudioManager.OnAudioFocusChangeListener;
 import android.media.MediaPlayer;
-import android.media.session.PlaybackState;
-
 import android.os.Handler;
 import android.os.Message;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
 import com.android.bluetooth.R;
@@ -183,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.
@@ -228,7 +229,7 @@
 
             case DELAYED_PAUSE:
                 if (BluetoothMediaBrowserService.getPlaybackState()
-                            == PlaybackState.STATE_PLAYING && !inCallFromStreamingDevice()) {
+                            == PlaybackStateCompat.STATE_PLAYING && !inCallFromStreamingDevice()) {
                     sendAvrcpPause();
                     mSentPause = true;
                     mStreamAvailable = false;
@@ -245,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() {
@@ -277,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.
@@ -286,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);
@@ -313,7 +314,6 @@
     private synchronized void abandonAudioFocus() {
         if (DBG) Log.d(TAG, "abandonAudioFocus()");
         stopFluorideStreaming();
-        releaseMediaKeyFocus();
         mAudioManager.abandonAudioFocus(mAudioFocusListener);
         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
     }
@@ -336,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);
     }
 
@@ -362,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/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 66571c4..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;
 
@@ -218,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;
     }
 
@@ -298,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");
             }
@@ -315,14 +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,
-                            getAbsVolumeResponse(), mVolumeNotificationLabel);
+                            getAbsVolume(), mVolumeNotificationLabel);
                     return true;
 
                 case MESSAGE_GET_FOLDER_ITEMS:
@@ -338,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);
@@ -347,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;
 
@@ -378,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;
@@ -435,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
@@ -554,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);
@@ -576,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)
@@ -674,31 +710,60 @@
         @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, getAbsVolumeResponse(), label);
     }
 
-    private int getAbsVolumeResponse() {
+    private int getAbsVolume() {
         if (mIsVolumeFixed) {
             return ABS_VOL_BASE;
         }
@@ -708,7 +773,7 @@
         return newIndex;
     }
 
-    MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
+    MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {
         @Override
         public void onPlay() {
             logD("onPlay");
@@ -781,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 304d5a2..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);
@@ -181,19 +215,19 @@
      */
     public static synchronized int getPlaybackState() {
         if (sBluetoothMediaBrowserService != null) {
-            PlaybackState currentPlaybackState =
+            PlaybackStateCompat currentPlaybackState =
                     sBluetoothMediaBrowserService.mSession.getController().getPlaybackState();
             if (currentPlaybackState != null) {
                 return currentPlaybackState.getState();
             }
         }
-        return PlaybackState.STATE_ERROR;
+        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 {
@@ -212,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 009e42c..a65bcfd 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -1559,7 +1559,6 @@
             if (service == null) {
                 return false;
             }
-            service.disable();
             return service.factoryReset();
 
         }
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/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 e2e35be..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;
@@ -95,9 +106,19 @@
     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:
@@ -369,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 =
@@ -419,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/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index c759a8a..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,21 @@
     }
 
     @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();
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
index 4fedc88..907f0dc 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -23,9 +23,11 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.media.AudioManager;
-import android.media.session.MediaController;
 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;
@@ -34,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;
 
@@ -66,15 +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
@@ -90,9 +101,14 @@
 
         // 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();
@@ -113,7 +129,7 @@
         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) {
             return;
         }
-        TestUtils.clearAdapterService(mAdapterService);
+        TestUtils.clearAdapterService(mA2dpAdapterService);
     }
 
     /**
@@ -141,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());
     }
 
     /**
@@ -149,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();
@@ -166,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());
     }
 
     /**
@@ -176,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();
@@ -193,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());
     }
 
     /**
@@ -221,7 +254,7 @@
     @Test
     public void testPlay() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Play
@@ -240,7 +273,7 @@
     @Test
     public void testPause() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Pause
@@ -259,7 +292,7 @@
     @Test
     public void testStop() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Stop
@@ -278,7 +311,7 @@
     @Test
     public void testNext() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Next
@@ -298,7 +331,7 @@
     @Test
     public void testPrevious() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Previous
@@ -318,7 +351,7 @@
     @Test
     public void testFastForward() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //FastForward
@@ -339,7 +372,7 @@
     @Test
     public void testRewind() throws Exception {
         setUpConnectedState(true, true);
-        MediaController.TransportControls transportControls =
+        MediaControllerCompat.TransportControls transportControls =
                 BluetoothMediaBrowserService.getTransportControls();
 
         //Rewind
@@ -355,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
@@ -483,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,
@@ -507,6 +603,45 @@
     }
 
     /**
+     * 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/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());
+    }
+}