Merge changes from topic "BLE debug information implementation"

* changes:
  BLE: GattService debug infomation implementation
  BLE: Rename existing variables in AppScanStats
diff --git a/Android.mk b/Android.mk
index f4a0d5d..0f80f52 100644
--- a/Android.mk
+++ b/Android.mk
@@ -26,6 +26,7 @@
         services.net \
         libprotobuf-java-lite \
         bluetooth-protos-lite \
+        guava \
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
         androidx.core_core \
diff --git a/jni/Android.bp b/jni/Android.bp
index 39b9f3f..e0034b1 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -25,11 +25,9 @@
         "libchrome",
         "libnativehelper",
         "liblog",
-        "libutils",
     ],
     static_libs: [
         "libbluetooth-types",
-        "libcutils",
     ],
     cflags: [
         "-Wall",
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 34eb2bb..6908b2f 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -20,7 +20,6 @@
 #include "utils/Log.h"
 #include "utils/misc.h"
 
-#include <cutils/properties.h>
 #include <dlfcn.h>
 #include <errno.h>
 #include <pthread.h>
@@ -598,17 +597,12 @@
     acquire_wake_lock_callout, release_wake_lock_callout,
 };
 
-#define PROPERTY_BT_LIBRARY_NAME "ro.bluetooth.library_name"
-#define DEFAULT_BT_LIBRARY_NAME "libbluetooth.so"
-
 int hal_util_load_bt_library(const bt_interface_t** interface) {
   const char* sym = BLUETOOTH_INTERFACE_STRING;
   bt_interface_t* itf = nullptr;
 
   // The library name is not set by default, so the preset library name is used.
-  char path[PROPERTY_VALUE_MAX] = "";
-  property_get(PROPERTY_BT_LIBRARY_NAME, path, DEFAULT_BT_LIBRARY_NAME);
-  void* handle = dlopen(path, RTLD_NOW);
+  void* handle = dlopen("libbluetooth.so", RTLD_NOW);
   if (!handle) {
     const char* err_str = dlerror();
     ALOGE("%s: failed to load Bluetooth library, error=%s", __func__,
@@ -1261,7 +1255,9 @@
     goto done;
   }
   uuidBytes = env->GetByteArrayElements(uuid, nullptr);
-  nativeServiceName = env->GetStringUTFChars(serviceName, nullptr);
+  if (serviceName != nullptr) {
+    nativeServiceName = env->GetStringUTFChars(serviceName, nullptr);
+  }
   if (uuidBytes == nullptr) {
     jniThrowIOException(env, EINVAL);
     goto done;
diff --git a/src/com/android/bluetooth/AlertActivity.java b/src/com/android/bluetooth/AlertActivity.java
new file mode 100644
index 0000000..6de2cec
--- /dev/null
+++ b/src/com/android/bluetooth/AlertActivity.java
@@ -0,0 +1,115 @@
+/*
+ * 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 android.bluetooth;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * An activity that follows the visual style of an AlertDialog.
+ *
+ * @see #mAlert
+ * @see #setupAlert()
+ */
+public abstract class AlertActivity extends Activity implements DialogInterface.OnDismissListener,
+        DialogInterface.OnCancelListener {
+
+    /**
+     * The model for the alert.
+     *
+     */
+    protected AlertDialog.Builder mAlertBuilder;
+    private AlertDialog mAlert;
+
+    public AlertActivity() {}
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        mAlertBuilder = new AlertDialog.Builder(this);
+        mAlertBuilder.setOnDismissListener(this);
+        mAlertBuilder.setOnCancelListener(this);
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return dispatchPopulateAccessibilityEvent(this, event);
+    }
+
+    private static boolean dispatchPopulateAccessibilityEvent(Activity act,
+            AccessibilityEvent event) {
+        event.setClassName(Dialog.class.getName());
+        event.setPackageName(act.getPackageName());
+
+        ViewGroup.LayoutParams params = act.getWindow().getAttributes();
+        boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT)
+                && (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
+        event.setFullScreen(isFullScreen);
+
+        return false;
+    }
+
+    protected void setupAlert() {
+        mAlert = mAlertBuilder.create();
+        mAlert.show();
+    }
+
+    protected void changeIconAttribute(int attrId) {
+        if (mAlert == null) return;
+        mAlert.setIconAttribute(attrId);
+    }
+    protected void changeTitle(CharSequence title) {
+        if (mAlert == null) return;
+        mAlert.setTitle(title);
+    }
+
+    protected void changeButtonVisibility(int identifier, int visibility) {
+        if (mAlert == null) return;
+        mAlert.getButton(identifier).setVisibility(visibility);
+    }
+
+    protected void changeButtonText(int identifier, CharSequence text) {
+        if (mAlert == null) return;
+        mAlert.getButton(identifier).setText(text);
+    }
+
+    protected void changeButtonEnabled(int identifier, boolean enable) {
+        if (mAlert == null) return;
+        mAlert.getButton(identifier).setEnabled(enable);
+    }
+}
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 4432067..cb098ac 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -16,7 +16,6 @@
 
 package com.android.bluetooth;
 
-import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -41,7 +40,9 @@
 import java.nio.ByteOrder;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetDecoder;
-import java.util.List;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -468,4 +469,14 @@
     public static String getUidPidString() {
         return "uid/pid=" + Binder.getCallingUid() + "/" + Binder.getCallingPid();
     }
+
+    /**
+     * Get system local time
+     *
+     * @return "MM-dd HH:mm:ss.SSS"
+     */
+    public static String getLocalTimeString() {
+        return DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS")
+                .withZone(ZoneId.systemDefault()).format(Instant.now());
+    }
 }
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index cebf767..167fc10 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -269,7 +269,13 @@
         }
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects A2dp for the remote bluetooth device
+     *
+     * @param device is the device with which we would like to disconnect a2dp
+     * @return true if profile disconnected, false if device not connected over a2dp
+     */
+    public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "disconnect(): " + device);
@@ -427,19 +433,9 @@
         }
     }
 
-    private void storeActiveDeviceVolume() {
-        // Make sure volume has been stored before been removed from active.
-        if (mFactory.getAvrcpTargetService() != null && mActiveDevice != null) {
-            mFactory.getAvrcpTargetService().storeVolumeForDevice(mActiveDevice);
-        }
-    }
-
     private void removeActiveDevice(boolean forceStopPlayingAudio) {
         BluetoothDevice previousActiveDevice = mActiveDevice;
         synchronized (mStateMachines) {
-            // Make sure volume has been store before device been remove from active.
-            storeActiveDeviceVolume();
-
             // This needs to happen before we inform the audio manager that the device
             // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
             updateAndBroadcastActiveDevice(null);
@@ -494,22 +490,6 @@
     }
 
     /**
-     * Early notification that Hearing Aids will be the active device. This allows the A2DP to save
-     * its volume before the Audio Service starts changing its media stream.
-     */
-    public void earlyNotifyHearingAidActive() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
-        synchronized (mStateMachines) {
-            // Switch active device from A2DP to Hearing Aids.
-            if (DBG) {
-                Log.d(TAG, "earlyNotifyHearingAidActive: Save volume for " + mActiveDevice);
-            }
-            storeActiveDeviceVolume();
-        }
-    }
-
-    /**
      * Set the active device.
      *
      * @param device the active device
@@ -548,14 +528,6 @@
             codecStatus = sm.getCodecStatus();
 
             boolean deviceChanged = !Objects.equals(device, mActiveDevice);
-            if (deviceChanged) {
-                // Switch from one A2DP to another A2DP device
-                if (DBG) {
-                    Log.d(TAG, "Switch A2DP devices to " + device + " from " + mActiveDevice);
-                }
-                storeActiveDeviceVolume();
-            }
-
             // This needs to happen before we inform the audio manager that the device
             // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
             updateAndBroadcastActiveDevice(device);
diff --git a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index 61ed75e..1a2bba7 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -243,24 +243,6 @@
     }
 
     /**
-     * Store the current system volume for a device in order to be retrieved later.
-     */
-    public void storeVolumeForDevice(BluetoothDevice device) {
-        if (device == null) return;
-
-        List<BluetoothDevice> HAActiveDevices = null;
-        if (mFactory.getHearingAidService() != null) {
-            HAActiveDevices = mFactory.getHearingAidService().getActiveDevices();
-        }
-        if (HAActiveDevices != null
-                && (HAActiveDevices.get(0) != null || HAActiveDevices.get(1) != null)) {
-            Log.d(TAG, "Do not store volume when Hearing Aid devices is active");
-            return;
-        }
-        mVolumeManager.storeVolumeForDevice(device);
-    }
-
-    /**
      * Remove the stored volume for a device.
      */
     public void removeStoredVolumeForDevice(BluetoothDevice device) {
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index ae94a4d..e3abc4d 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -223,7 +223,6 @@
         mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
         BluetoothMediaBrowserService.notifyChanged(mService
                 .sBrowseTree.mRootNode);
-        BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
         mBrowsingConnected = true;
     }
 
@@ -233,12 +232,10 @@
         mAddressedPlayer.updateCurrentTrack(null);
         mBrowseTree.mNowPlayingNode.setCached(false);
         BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
-        BluetoothMediaBrowserService.addressedPlayerChanged(null);
         mService.sBrowseTree.mRootNode.removeChild(
                 mBrowseTree.mRootNode);
         BluetoothMediaBrowserService.notifyChanged(mService
                 .sBrowseTree.mRootNode);
-        BluetoothMediaBrowserService.trackChanged(null);
         mBrowsingConnected = false;
     }
 
@@ -299,6 +296,7 @@
         @Override
         public void enter() {
             if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
+                BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
                 BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
             } else {
@@ -712,6 +710,8 @@
         @Override
         public void enter() {
             onBrowsingDisconnected();
+            BluetoothMediaBrowserService.trackChanged(null);
+            BluetoothMediaBrowserService.addressedPlayerChanged(null);
             broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
             transitionTo(mDisconnected);
         }
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index e0452bf..e4a5ae0 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -27,6 +27,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
 import android.bluetooth.IBluetoothMetadataListener;
@@ -68,10 +69,23 @@
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.btservice.storage.MetadataDatabase;
 import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.hearingaid.HearingAidService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+import com.android.bluetooth.hid.HidDeviceService;
+import com.android.bluetooth.hid.HidHostService;
+import com.android.bluetooth.map.BluetoothMapService;
+import com.android.bluetooth.mapclient.MapClientService;
+import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.pbap.BluetoothPbapService;
+import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.sap.SapService;
 import com.android.bluetooth.sdp.SdpManager;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -200,6 +214,20 @@
 
     private BluetoothSocketManagerBinder mBluetoothSocketManagerBinder;
 
+    private A2dpService mA2dpService;
+    private A2dpSinkService mA2dpSinkService;
+    private HeadsetService mHeadsetService;
+    private HeadsetClientService mHeadsetClientService;
+    private BluetoothMapService mMapService;
+    private MapClientService mMapClientService;
+    private HidDeviceService mHidDeviceService;
+    private HidHostService mHidHostService;
+    private PanService mPanService;
+    private BluetoothPbapService mPbapService;
+    private PbapClientService mPbapClientService;
+    private HearingAidService mHearingAidService;
+    private SapService mSapService;
+
     /**
      * Register a {@link ProfileService} with AdapterService.
      *
@@ -294,6 +322,7 @@
                         mAdapterProperties.onBluetoothReady();
                         updateUuids();
                         setBluetoothClassFromConfig();
+                        initProfileServices();
                         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS);
                         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE);
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
@@ -769,6 +798,229 @@
         }
     }
 
+    /**
+     * Verifies whether the profile is supported by the local bluetooth adapter by checking a
+     * bitmask of its supported profiles
+     *
+     * @param remoteDeviceUuids is an array of all supported profiles by the remote device
+     * @param localDeviceUuids  is an array of all supported profiles by the local device
+     * @param profile           is the profile we are checking for support
+     * @param device            is the remote device we wish to connect to
+     * @return true if the profile is supported by both the local and remote device, false otherwise
+     */
+    private boolean isSupported(ParcelUuid[] localDeviceUuids, ParcelUuid[] remoteDeviceUuids,
+            int profile, BluetoothDevice device) {
+        if (remoteDeviceUuids == null || remoteDeviceUuids.length == 0) {
+            Log.e(TAG, "isSupported: Remote Device Uuids Empty");
+        }
+
+        if (profile == BluetoothProfile.HEADSET) {
+            return (BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.HSP_AG)
+                    && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.HSP))
+                    || (BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.Handsfree_AG)
+                    && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Handsfree));
+        }
+        if (profile == BluetoothProfile.HEADSET_CLIENT) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Handsfree_AG)
+                    && BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.Handsfree);
+        }
+        if (profile == BluetoothProfile.A2DP) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AdvAudioDist)
+                    || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AudioSink);
+        }
+        if (profile == BluetoothProfile.A2DP_SINK) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AdvAudioDist)
+                    || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AudioSource);
+        }
+        if (profile == BluetoothProfile.OPP) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.ObexObjectPush);
+        }
+        if (profile == BluetoothProfile.HID_HOST) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Hid)
+                    || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Hogp);
+        }
+        if (profile == BluetoothProfile.HID_DEVICE) {
+            return mHidDeviceService.getConnectionState(device)
+                    == BluetoothProfile.STATE_DISCONNECTED;
+        }
+        if (profile == BluetoothProfile.PAN) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.NAP);
+        }
+        if (profile == BluetoothProfile.MAP) {
+            return mMapService.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
+        }
+        if (profile == BluetoothProfile.PBAP) {
+            return mPbapService.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
+        }
+        if (profile == BluetoothProfile.MAP_CLIENT) {
+            return true;
+        }
+        if (profile == BluetoothProfile.PBAP_CLIENT) {
+            return BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.PBAP_PCE)
+                    && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.PBAP_PSE);
+        }
+        if (profile == BluetoothProfile.HEARING_AID) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.HearingAid);
+        }
+        if (profile == BluetoothProfile.SAP) {
+            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.SAP);
+        }
+
+        Log.e(TAG, "isSupported: Unexpected profile passed in to function: " + profile);
+        return false;
+    }
+
+    /**
+     * Checks if any profile is enabled for the given device
+     *
+     * @param device is the device for which we are checking if any profiles are enabled
+     * @return true if any profile is enabled, false otherwise
+     */
+    private boolean isAnyProfileEnabled(BluetoothDevice device) {
+
+        if (mA2dpService != null
+                && mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mA2dpSinkService != null
+                && mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mHeadsetService != null
+                && mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mHeadsetClientService != null
+                && mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mMapClientService != null
+                && mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mHidHostService != null
+                && mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mPanService != null
+                && mPanService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mPbapClientService != null
+                && mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+        if (mHearingAidService != null
+                && mHearingAidService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Connects only available profiles (those with {@link BluetoothProfile#PRIORITY_ON})
+     *
+     * @param device is the device with which we are connecting the profiles
+     * @return true
+     */
+    private boolean connectEnabledProfiles(BluetoothDevice device) {
+        ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
+        ParcelUuid[] localDeviceUuids = getUuids();
+
+        if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.A2DP, device)
+                && mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting A2dp");
+            mA2dpService.connect(device);
+        }
+        if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.A2DP_SINK, device)
+                && mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting A2dp Sink");
+            mA2dpSinkService.connect(device);
+        }
+        if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEADSET, device)
+                && mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting Headset Profile");
+            mHeadsetService.connect(device);
+        }
+        if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEADSET_CLIENT, device)
+                && mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting HFP");
+            mHeadsetClientService.connect(device);
+        }
+        if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.MAP_CLIENT, device)
+                && mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting MAP");
+            mMapClientService.connect(device);
+        }
+        if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HID_HOST, device)
+                && mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting Hid Host Profile");
+            mHidHostService.connect(device);
+        }
+        if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.PAN, device)
+                && mPanService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting Pan Profile");
+            mPanService.connect(device);
+        }
+        if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.PBAP_CLIENT, device)
+                && mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting Pbap");
+            mPbapClientService.connect(device);
+        }
+        if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEARING_AID, device)
+                && mHearingAidService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+            Log.i(TAG, "connectEnabledProfiles: Connecting Hearing Aid Profile");
+            mHearingAidService.connect(device);
+        }
+
+        return true;
+    }
+
+    /**
+     * Verifies that all bluetooth profile services are running
+     *
+     * @return true if all bluetooth profile services running, false otherwise
+     */
+    private boolean profileServicesRunning() {
+        if (mRegisteredProfiles.size() == Config.getSupportedProfiles().length
+                && mRegisteredProfiles.size() == mRunningProfiles.size()) {
+            return true;
+        }
+
+        Log.e(TAG, "profileServicesRunning: One or more supported services not running");
+        return false;
+    }
+
+    /**
+     * Initializes all the profile services fields
+     */
+    private void initProfileServices() {
+        Log.i(TAG, "initProfileServices: Initializing all bluetooth profile services");
+        mA2dpService = A2dpService.getA2dpService();
+        mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
+        mHeadsetService = HeadsetService.getHeadsetService();
+        mHeadsetClientService = HeadsetClientService.getHeadsetClientService();
+        mMapService = BluetoothMapService.getBluetoothMapService();
+        mMapClientService = MapClientService.getMapClientService();
+        mHidDeviceService = HidDeviceService.getHidDeviceService();
+        mHidHostService = HidHostService.getHidHostService();
+        mPanService = PanService.getPanService();
+        mPbapService = BluetoothPbapService.getBluetoothPbapService();
+        mPbapClientService = PbapClientService.getPbapClientService();
+        mHearingAidService = HearingAidService.getHearingAidService();
+        mSapService = SapService.getSapService();
+    }
+
     private boolean isAvailable() {
         return !mCleaningUp;
     }
@@ -1240,6 +1492,34 @@
         }
 
         @Override
+        public boolean connectAllEnabledProfiles(BluetoothDevice device) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "connectAllEnabledProfiles() - Not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.connectAllEnabledProfiles(device);
+        }
+
+        @Override
+        public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "disconnectAllEnabledProfiles() - Not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.disconnectAllEnabledProfiles(device);
+        }
+
+        @Override
         public String getRemoteName(BluetoothDevice device) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getRemoteName() - Not allowed for non-active user");
@@ -2144,6 +2424,202 @@
     }
 
     /**
+     * Connects all enabled and supported bluetooth profiles between the local and remote device
+     *
+     * @param device is the remote device with which to connect these profiles
+     * @return true if all profiles successfully connected, false if an error occurred
+     */
+    public boolean connectAllEnabledProfiles(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (!profileServicesRunning()) {
+            Log.e(TAG, "connectAllEnabledProfiles: Not all profile services running");
+            return false;
+        }
+
+        // Checks if any profiles are enabled and if so, only connect enabled profiles
+        if (isAnyProfileEnabled(device)) {
+            return connectEnabledProfiles(device);
+        }
+
+        int numProfilesConnected = 0;
+        ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
+        ParcelUuid[] localDeviceUuids = getUuids();
+
+        // All profile toggles disabled, so connects all supported profiles
+        if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.A2DP, device)) {
+            mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting A2dp");
+            mA2dpService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.A2DP_SINK, device)) {
+            mA2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting A2dp Sink");
+            mA2dpSinkService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEADSET, device)) {
+            mHeadsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting Headset Profile");
+            mHeadsetService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEADSET_CLIENT, device)) {
+            mHeadsetClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting HFP");
+            mHeadsetClientService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.MAP_CLIENT, device)) {
+            mMapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting MAP");
+            mMapClientService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HID_HOST, device)) {
+            mHidHostService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            Log.i(TAG,
+                    "connectAllEnabledProfiles: Connecting Hid Host Profile");
+            mHidHostService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.PAN, device)) {
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting Pan Profile");
+            mPanService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.PBAP_CLIENT, device)) {
+            mPbapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting Pbap");
+            mPbapClientService.connect(device);
+            numProfilesConnected++;
+        }
+        if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEARING_AID, device)) {
+            Log.i(TAG,
+                    "connectAllEnabledProfiles: Connecting Hearing Aid Profile");
+            mHearingAidService.connect(device);
+            numProfilesConnected++;
+        }
+
+        Log.i(TAG, "connectAllEnabledProfiles: Number of Profiles Connected: "
+                + numProfilesConnected);
+
+        return true;
+    }
+
+    /**
+     * Disconnects all enabled and supported bluetooth profiles between the local and remote device
+     *
+     * @param device is the remote device with which to disconnect these profiles
+     * @return true if all profiles successfully disconnected, false if an error occurred
+     */
+    public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (!profileServicesRunning()) {
+            Log.e(TAG, "disconnectAllEnabledProfiles: Not all profile services bound");
+            return false;
+        }
+
+        ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
+        ParcelUuid[] localDeviceUuids = getUuids();
+
+        if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.A2DP, device)) {
+            if (mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp");
+            mA2dpService.disconnect(device);
+        }
+        if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.A2DP_SINK, device)) {
+            if (mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mA2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp Sink");
+            mA2dpSinkService.disconnect(device);
+        }
+        if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEADSET, device)) {
+            if (mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mHeadsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+
+            Log.i(TAG,
+                    "disconnectAllEnabledProfiles: Disconnecting Headset Profile");
+            mHeadsetService.disconnect(device);
+        }
+        if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEADSET_CLIENT, device)) {
+            if (mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mHeadsetClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting HFP");
+            mHeadsetClientService.disconnect(device);
+        }
+        if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.MAP_CLIENT, device)) {
+            if (mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mMapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP");
+            mMapClientService.disconnect(device);
+        }
+        if (mHidDeviceService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HID_DEVICE, device)) {
+            Log.i(TAG,
+                    "disconnectAllEnabledProfiles: Disconnecting Hid Device "
+                            + "Profile");
+            mHidDeviceService.disconnect(device);
+        }
+        if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HID_HOST, device)) {
+            if (mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mHidHostService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+
+            Log.i(TAG,
+                    "disconnectAllEnabledProfiles: Disconnecting Hid Host Profile");
+            mHidHostService.disconnect(device);
+        }
+        if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.PAN, device)) {
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pan Profile");
+            mPanService.disconnect(device);
+        }
+        if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.PBAP_CLIENT, device)) {
+            if (mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mPbapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap");
+            mPbapClientService.disconnect(device);
+        }
+        if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.HEARING_AID, device)) {
+            Log.i(TAG,
+                    "disconnectAllEnabledProfiles: Disconnecting Hearing Aid Profile");
+            mHearingAidService.disconnect(device);
+        }
+
+        return true;
+    }
+
+    /**
      * Same as API method {@link BluetoothDevice#getName()}
      *
      * @param device remote device of interest
@@ -2794,6 +3270,7 @@
             profile.dump(sb);
         }
         mSilenceDeviceManager.dump(fd, writer, args);
+        mDatabaseManager.dump(writer);
 
         writer.write(sb.toString());
         writer.flush();
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 382841a..3320f0e 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -519,7 +519,8 @@
                 BluetoothDevice.BOND_BONDING,
                 BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REQUESTED, 0);
 
-        infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" + cod);
+        infoLog("pinRequestCallback: " + bdDevice.getAddress()
+                + " name:" + bdDevice.getName() + " cod:" + new BluetoothClass(cod));
 
         Message msg = obtainMessage(PIN_REQUEST);
         msg.obj = bdDevice;
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 2b1f46c..0ec5b47 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -127,8 +127,8 @@
                 Log.v(TAG, "Adding " + config.mClass.getSimpleName());
                 profiles.add(config.mClass);
             }
-            sSupportedProfiles = profiles.toArray(new Class[profiles.size()]);
         }
+        sSupportedProfiles = profiles.toArray(new Class[profiles.size()]);
     }
 
     static Class[] getSupportedProfiles() {
diff --git a/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
index d0bddf1..e1033b9 100644
--- a/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
@@ -37,4 +37,51 @@
     public byte[] untethered_right_charging;
     public byte[] untethered_case_charging;
     public byte[] enhanced_settings_ui_uri;
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("manufacturer_name=")
+                .append(metadataToString(manufacturer_name))
+                .append("|model_name=")
+                .append(metadataToString(model_name))
+                .append("|software_version=")
+                .append(metadataToString(software_version))
+                .append("|hardware_version=")
+                .append(metadataToString(hardware_version))
+                .append("|companion_app=")
+                .append(metadataToString(companion_app))
+                .append("|main_icon=")
+                .append(metadataToString(main_icon))
+                .append("|is_untethered_headset=")
+                .append(metadataToString(is_untethered_headset))
+                .append("|untethered_left_icon=")
+                .append(metadataToString(untethered_left_icon))
+                .append("|untethered_right_icon=")
+                .append(metadataToString(untethered_right_icon))
+                .append("|untethered_case_icon=")
+                .append(metadataToString(untethered_case_icon))
+                .append("|untethered_left_battery=")
+                .append(metadataToString(untethered_left_battery))
+                .append("|untethered_right_battery=")
+                .append(metadataToString(untethered_right_battery))
+                .append("|untethered_case_battery=")
+                .append(metadataToString(untethered_case_battery))
+                .append("|untethered_left_charging=")
+                .append(metadataToString(untethered_left_charging))
+                .append("|untethered_right_charging=")
+                .append(metadataToString(untethered_right_charging))
+                .append("|untethered_case_charging=")
+                .append(metadataToString(untethered_case_charging))
+                .append("|enhanced_settings_ui_uri=")
+                .append(metadataToString(enhanced_settings_ui_uri));
+
+        return builder.toString();
+    }
+
+    private String metadataToString(byte[] metadata) {
+        if (metadata == null) {
+            return null;
+        }
+        return new String(metadata);
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index a0e3c5a..f86adbd 100644
--- a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -39,6 +39,9 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.internal.annotations.VisibleForTesting;
 
+import com.google.common.collect.EvictingQueue;
+
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -52,8 +55,6 @@
  * for Bluetooth persistent data.
  */
 public class DatabaseManager {
-    private static final boolean DBG = true;
-    private static final boolean VERBOSE = true;
     private static final String TAG = "BluetoothDatabase";
 
     private AdapterService mAdapterService = null;
@@ -65,6 +66,8 @@
     @VisibleForTesting
     final Map<String, Metadata> mMetadataCache = new HashMap<>();
     private final Semaphore mSemaphore = new Semaphore(1);
+    private static final int METADATA_CHANGED_LOG_MAX_SIZE = 20;
+    private final EvictingQueue<String> mMetadataChangedLog;
 
     private static final int LOAD_DATABASE_TIMEOUT = 500; // milliseconds
     private static final int MSG_LOAD_DATABASE = 0;
@@ -78,6 +81,7 @@
      */
     public DatabaseManager(AdapterService service) {
         mAdapterService = service;
+        mMetadataChangedLog = EvictingQueue.create(METADATA_CHANGED_LOG_MAX_SIZE);
     }
 
     class DatabaseHandler extends Handler {
@@ -218,21 +222,17 @@
             }
 
             String address = device.getAddress();
-            if (VERBOSE) {
-                Log.d(TAG, "setCustomMeta: " + address + ", key=" + key);
-            }
             if (!mMetadataCache.containsKey(address)) {
                 createMetadata(address);
             }
             Metadata data = mMetadataCache.get(address);
             byte[] oldValue = data.getCustomizedMeta(key);
             if (oldValue != null && Arrays.equals(oldValue, newValue)) {
-                if (VERBOSE) {
-                    Log.d(TAG, "setCustomMeta: metadata not changed.");
-                }
+                Log.v(TAG, "setCustomMeta: metadata not changed.");
                 return true;
             }
             logManufacturerInfo(device, key, newValue);
+            logMetadataChange(address, "setCustomMeta key=" + key);
             data.setCustomizedMeta(key, newValue);
 
             updateDatabase(data);
@@ -259,7 +259,7 @@
             String address = device.getAddress();
 
             if (!mMetadataCache.containsKey(address)) {
-                Log.e(TAG, "getCustomMeta: device " + address + " is not in cache");
+                Log.d(TAG, "getCustomMeta: device " + address + " is not in cache");
                 return null;
             }
 
@@ -302,10 +302,6 @@
             }
 
             String address = device.getAddress();
-            if (VERBOSE) {
-                Log.v(TAG, "setProfilePriority: " + address + ", profile=" + profile
-                        + ", priority = " + newPriority);
-            }
             if (!mMetadataCache.containsKey(address)) {
                 if (newPriority == BluetoothProfile.PRIORITY_UNDEFINED) {
                     return true;
@@ -315,11 +311,12 @@
             Metadata data = mMetadataCache.get(address);
             int oldPriority = data.getProfilePriority(profile);
             if (oldPriority == newPriority) {
-                if (VERBOSE) {
-                    Log.v(TAG, "setProfilePriority priority not changed.");
-                }
+                Log.v(TAG, "setProfilePriority priority not changed.");
                 return true;
             }
+            String profileStr = BluetoothProfile.getProfileName(profile);
+            logMetadataChange(address, profileStr + " priority changed: "
+                    + ": " + oldPriority + " -> " + newPriority);
 
             data.setProfilePriority(profile, newPriority);
             updateDatabase(data);
@@ -355,16 +352,14 @@
             String address = device.getAddress();
 
             if (!mMetadataCache.containsKey(address)) {
-                Log.e(TAG, "getProfilePriority: device " + address + " is not in cache");
+                Log.d(TAG, "getProfilePriority: device " + address + " is not in cache");
                 return BluetoothProfile.PRIORITY_UNDEFINED;
             }
 
             Metadata data = mMetadataCache.get(address);
             int priority = data.getProfilePriority(profile);
-            if (VERBOSE) {
-                Log.v(TAG, "getProfilePriority: " + address + ", profile=" + profile
-                        + ", priority = " + priority);
-            }
+            Log.v(TAG, "getProfilePriority: " + address + ", profile=" + profile
+                    + ", priority = " + priority);
             return priority;
         }
     }
@@ -402,6 +397,8 @@
             if (oldValue == newValue) {
                 return;
             }
+            logMetadataChange(address, "Supports optional codec changed: "
+                    + oldValue + " -> " + newValue);
 
             data.a2dpSupportsOptionalCodecs = newValue;
             updateDatabase(data);
@@ -428,7 +425,7 @@
             String address = device.getAddress();
 
             if (!mMetadataCache.containsKey(address)) {
-                Log.e(TAG, "getA2dpOptionalCodec: device " + address + " is not in cache");
+                Log.d(TAG, "getA2dpOptionalCodec: device " + address + " is not in cache");
                 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
             }
 
@@ -470,6 +467,8 @@
             if (oldValue == newValue) {
                 return;
             }
+            logMetadataChange(address, "Enable optional codec changed: "
+                    + oldValue + " -> " + newValue);
 
             data.a2dpOptionalCodecsEnabled = newValue;
             updateDatabase(data);
@@ -496,7 +495,7 @@
             String address = device.getAddress();
 
             if (!mMetadataCache.containsKey(address)) {
-                Log.e(TAG, "getA2dpOptionalCodecEnabled: device " + address + " is not in cache");
+                Log.d(TAG, "getA2dpOptionalCodecEnabled: device " + address + " is not in cache");
                 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
             }
 
@@ -525,10 +524,7 @@
      * @param database the Bluetooth storage {@link MetadataDatabase}
      */
     public void start(MetadataDatabase database) {
-        if (DBG) {
-            Log.d(TAG, "start()");
-        }
-
+        Log.d(TAG, "start()");
         if (mAdapterService == null) {
             Log.e(TAG, "stat failed, mAdapterService is null.");
             return;
@@ -582,12 +578,10 @@
     }
 
     void createMetadata(String address) {
-        if (VERBOSE) {
-            Log.v(TAG, "createMetadata " + address);
-        }
         Metadata data = new Metadata(address);
         mMetadataCache.put(address, data);
         updateDatabase(data);
+        logMetadataChange(address, "Metadata created");
     }
 
     @VisibleForTesting
@@ -623,14 +617,10 @@
             mMigratedFromSettingsGlobal = true;
             for (Metadata data : list) {
                 String address = data.getAddress();
-                if (VERBOSE) {
-                    Log.v(TAG, "cacheMetadata: found device " + address);
-                }
+                Log.v(TAG, "cacheMetadata: found device " + address);
                 mMetadataCache.put(address, data);
             }
-            if (VERBOSE) {
-                Log.v(TAG, "cacheMetadata: Database is ready");
-            }
+            Log.i(TAG, "cacheMetadata: Database is ready");
         }
     }
 
@@ -726,9 +716,7 @@
     }
 
     private void loadDatabase() {
-        if (DBG) {
-            Log.d(TAG, "Load Database");
-        }
+        Log.d(TAG, "Load Database");
         Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE);
         mHandler.sendMessage(message);
         try {
@@ -744,20 +732,19 @@
             Log.e(TAG, "updateDatabase: address is null");
             return;
         }
-        if (DBG) {
-            Log.d(TAG, "updateDatabase " + data.getAddress());
-        }
+        Log.d(TAG, "updateDatabase " + data.getAddress());
         Message message = mHandler.obtainMessage(MSG_UPDATE_DATABASE);
         message.obj = data;
         mHandler.sendMessage(message);
     }
 
     private void deleteDatabase(Metadata data) {
-        if (data.getAddress() == null) {
+        String address = data.getAddress();
+        if (address == null) {
             Log.e(TAG, "deleteDatabase: address is null");
             return;
         }
-        Log.d(TAG, "deleteDatabase: " + data.getAddress());
+        logMetadataChange(address, "Metadata deleted");
         Message message = mHandler.obtainMessage(MSG_DELETE_DATABASE);
         message.obj = data.getAddress();
         mHandler.sendMessage(message);
@@ -793,4 +780,31 @@
                 BluetoothProtoEnums.DEVICE_INFO_EXTERNAL, callingApp, manufacturerName, modelName,
                 hardwareVersion, softwareVersion);
     }
+
+    private void logMetadataChange(String address, String log) {
+        String time = Utils.getLocalTimeString();
+        String uidPid = Utils.getUidPidString();
+        mMetadataChangedLog.add(time + " (" + uidPid + ") " + address + " " + log);
+    }
+
+    /**
+     * Dump database info to a PrintWriter
+     *
+     * @param writer the PrintWriter to write log
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("\nBluetoothDatabase:");
+        writer.println("  Metadata Changes:");
+        for (String log : mMetadataChangedLog) {
+            writer.println("    " + log);
+        }
+        writer.println("\nMetadata:");
+        for (HashMap.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
+            if (entry.getKey().equals(LOCAL_STORAGE)) {
+                // No need to dump local storage
+                continue;
+            }
+            writer.println("    " + entry.getValue());
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/storage/Metadata.java b/src/com/android/bluetooth/btservice/storage/Metadata.java
index b39333f..9151ede 100644
--- a/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -301,4 +301,20 @@
         }
         return list;
     }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(address)
+            .append(" {profile priority(")
+            .append(profilePriorities)
+            .append("), optional codec(support=")
+            .append(a2dpSupportsOptionalCodecs)
+            .append("|enabled=")
+            .append(a2dpOptionalCodecsEnabled)
+            .append("), custom metadata(")
+            .append(publicMetadata)
+            .append(")}");
+
+        return builder.toString();
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
index f4ea719..d87fbde 100644
--- a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -50,4 +50,22 @@
         hearing_aid_priority = BluetoothProfile.PRIORITY_UNDEFINED;
         map_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
     }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("A2DP=").append(a2dp_priority)
+                .append("|A2DP_SINK=").append(a2dp_sink_priority)
+                .append("|HEADSET=").append(hfp_priority)
+                .append("|HEADSET_CLIENT=").append(hfp_client_priority)
+                .append("|HID_HOST=").append(hid_host_priority)
+                .append("|PAN=").append(pan_priority)
+                .append("|PBAP=").append(pbap_priority)
+                .append("|PBAP_CLIENT=").append(pbap_client_priority)
+                .append("|MAP=").append(map_priority)
+                .append("|MAP_CLIENT=").append(map_client_priority)
+                .append("|SAP=").append(sap_priority)
+                .append("|HEARING_AID=").append(hearing_aid_priority);
+
+        return builder.toString();
+    }
 }
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index f29d9b9..3647a65 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -33,7 +33,6 @@
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
-import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
@@ -221,7 +220,13 @@
         sHearingAidService = instance;
     }
 
-    boolean connect(BluetoothDevice device) {
+    /**
+     * Connects the hearing aid profile to the passed in device
+     *
+     * @param device is the device with which we will connect the hearing aid profile
+     * @return true if hearing aid profile successfully connected, false otherwise
+     */
+    public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "connect(): " + device);
@@ -281,7 +286,13 @@
         return true;
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects hearing aid profile for the passed in device
+     *
+     * @param device is the device with which we want to disconnected the hearing aid profile
+     * @return true if hearing aid profile successfully disconnected, false otherwise
+     */
+    public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "disconnect(): " + device);
@@ -505,15 +516,6 @@
             Long deviceHiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
                     BluetoothHearingAid.HI_SYNC_ID_INVALID);
             if (deviceHiSyncId != mActiveDeviceHiSyncId) {
-                // Give an early notification to A2DP that active device is being switched
-                // to Hearing Aids before the Audio Service.
-                final A2dpService a2dpService = mFactory.getA2dpService();
-                if (a2dpService != null) {
-                    if (DBG) {
-                        Log.d(TAG, "earlyNotifyHearingAidActive for " + device);
-                    }
-                    a2dpService.earlyNotifyHearingAidActive();
-                }
                 mActiveDeviceHiSyncId = deviceHiSyncId;
                 reportActiveDevice(device);
             }
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 1dbafd0..8f9adae 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -729,7 +729,13 @@
         return true;
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects hfp from the passed in device
+     *
+     * @param device is the device with which we will disconnect hfp
+     * @return true if hfp is disconnected, false if the device is not connected
+     */
+    public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "disconnect: device=" + device + ", " + Utils.getUidPidString());
         synchronized (mStateMachines) {
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index 474d332..e7412fe 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -491,7 +491,13 @@
         return true;
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects hfp client for the remote bluetooth device
+     *
+     * @param device is the device with which we are attempting to disconnect the profile
+     * @return true if hfp client profile successfully disconnected, false otherwise
+     */
+    public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
diff --git a/src/com/android/bluetooth/hid/HidDeviceService.java b/src/com/android/bluetooth/hid/HidDeviceService.java
index eb0de8e..b2d7e41 100644
--- a/src/com/android/bluetooth/hid/HidDeviceService.java
+++ b/src/com/android/bluetooth/hid/HidDeviceService.java
@@ -592,7 +592,13 @@
                 && mHidDeviceNativeInterface.unplug();
     }
 
-    synchronized boolean connect(BluetoothDevice device) {
+    /**
+     * Connects the Hid device profile for the remote bluetooth device
+     *
+     * @param device is the device with which we would like to connect the hid device profile
+     * @return true if the connection is successful, false otherwise
+     */
+    public synchronized boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "connect(): device=" + device);
@@ -601,7 +607,13 @@
         return checkCallingUid() && mHidDeviceNativeInterface.connect(device);
     }
 
-    synchronized boolean disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects the hid device profile for the remote bluetooth device
+     *
+     * @param device is the device with which we would like to disconnect the hid device profile
+     * @return true if the disconnection is successful, false otherwise
+     */
+    public synchronized boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "disconnect(): device=" + device);
@@ -701,7 +713,15 @@
         sHidDeviceService = instance;
     }
 
-    int getConnectionState(BluetoothDevice device) {
+    /**
+     * Gets the connections state for the hid device profile for the passed in device
+     *
+     * @param device is the device whose conenction state we want to verify
+     * @return current connection state, one of {@link BluetoothProfile#STATE_DISCONNECTED},
+     * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
+     * {@link BluetoothProfile#STATE_DISCONNECTING}
+     */
+    public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mHidDevice != null && mHidDevice.equals(device)) {
             return mHidDeviceState;
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index c378f8e..2e0019c 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -468,7 +468,14 @@
     ;
 
     //APIs
-    boolean connect(BluetoothDevice device) {
+
+    /**
+     * Connects the hid host profile for the passed in device
+     *
+     * @param device is the device with which to connect the hid host profile
+     * @return true if connection is successful, false otherwise
+     */
+    public boolean connect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "connect: " + device.getAddress());
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (getConnectionState(device) != BluetoothHidHost.STATE_DISCONNECTED) {
@@ -485,7 +492,13 @@
         return true;
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects the hid host profile from the passed in device
+     *
+     * @param device is the device with which to disconnect the hid host profile
+     * @return true
+     */
+    public boolean disconnect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "disconnect: " + device.getAddress());
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT, device);
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 71942f0..1d651ba 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -509,7 +509,12 @@
         }
     }
 
-    void disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects MAP from the supplied device
+     *
+     * @param device is the device on which we want to disconnect MAP
+     */
+    public void disconnect(BluetoothDevice device) {
         mSessionStatusHandler.sendMessage(
                 mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device));
     }
@@ -563,7 +568,14 @@
         return deviceList;
     }
 
-    int getConnectionState(BluetoothDevice device) {
+    /**
+     * Gets the connection state of MAP with the passed in device.
+     *
+     * @param device is the device whose connection state we are querying
+     * @return {@link BluetoothProfile#STATE_CONNECTED} if MAP is connected to this device,
+     * {@link BluetoothProfile#STATE_DISCONNECTED} otherwise
+     */
+    public int getConnectionState(BluetoothDevice device) {
         synchronized (this) {
             if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
                 return BluetoothProfile.STATE_CONNECTED;
@@ -1197,18 +1209,6 @@
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
-            if (VERBOSE) {
-                Log.v(TAG, "connect()");
-            }
-            BluetoothMapService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return false;
-        }
-
-        @Override
         public boolean disconnect(BluetoothDevice device) {
             if (VERBOSE) {
                 Log.v(TAG, "disconnect()");
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
index 0253e24..8a4d149 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
@@ -32,6 +32,7 @@
 
 package com.android.bluetooth.opp;
 
+import android.bluetooth.AlertActivity;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
@@ -40,8 +41,6 @@
 import android.widget.Toast;
 
 import com.android.bluetooth.R;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
 
 /**
  * This class is designed to show BT enable confirmation dialog;
@@ -57,14 +56,12 @@
         // Set up the "dialog"
         mOppManager = BluetoothOppManager.getInstance(this);
         mOppManager.mSendingFlag = false;
-        final AlertController.AlertParams p = mAlertParams;
-        p.mIconAttrId = android.R.attr.alertDialogIcon;
-        p.mTitle = getString(R.string.bt_enable_title);
-        p.mView = createView();
-        p.mPositiveButtonText = getString(R.string.bt_enable_ok);
-        p.mPositiveButtonListener = this;
-        p.mNegativeButtonText = getString(R.string.bt_enable_cancel);
-        p.mNegativeButtonListener = this;
+
+        mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
+        mAlertBuilder.setTitle(getString(R.string.bt_enable_title));
+        mAlertBuilder.setView(createView());
+        mAlertBuilder.setPositiveButton(R.string.bt_enable_ok, this);
+        mAlertBuilder.setNegativeButton(R.string.bt_enable_cancel, this);
         setupAlert();
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java
index 4a9a710..cde530c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java
@@ -32,6 +32,7 @@
 
 package com.android.bluetooth.opp;
 
+import android.bluetooth.AlertActivity;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -46,8 +47,6 @@
 import android.widget.TextView;
 
 import com.android.bluetooth.R;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
 
 /**
  * This class is designed to show BT enabling progress.
@@ -80,10 +79,8 @@
         registerReceiver(mBluetoothReceiver, filter);
         mRegistered = true;
 
-        // Set up the "dialog"
-        final AlertController.AlertParams p = mAlertParams;
-        p.mTitle = getString(R.string.enabling_progress_title);
-        p.mView = createView();
+        mAlertBuilder.setTitle(R.string.enabling_progress_title);
+        mAlertBuilder.setView(createView());
         setupAlert();
 
         // Add timeout for enabling progress
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
index 22a8d82..f78e32d 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
@@ -32,6 +32,7 @@
 
 package com.android.bluetooth.opp;
 
+import android.bluetooth.AlertActivity;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
@@ -39,8 +40,6 @@
 import android.widget.TextView;
 
 import com.android.bluetooth.R;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
 
 /**
  * This class is designed to show BT error messages;
@@ -48,30 +47,26 @@
 public class BluetoothOppBtErrorActivity extends AlertActivity
         implements DialogInterface.OnClickListener {
 
-    private String mErrorContent;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         Intent intent = getIntent();
-        String mErrorTitle = intent.getStringExtra("title");
-        mErrorContent = intent.getStringExtra("content");
+        String errorTitle = intent.getStringExtra("title");
+        String errorContent = intent.getStringExtra("content");
 
         // Set up the "dialog"
-        final AlertController.AlertParams p = mAlertParams;
-        p.mIconAttrId = android.R.attr.alertDialogIcon;
-        p.mTitle = mErrorTitle;
-        p.mView = createView();
-        p.mPositiveButtonText = getString(R.string.bt_error_btn_ok);
-        p.mPositiveButtonListener = this;
+        mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
+        mAlertBuilder.setTitle(errorTitle);
+        mAlertBuilder.setView(createView(errorContent));
+        mAlertBuilder.setPositiveButton(R.string.bt_error_btn_ok, this);
         setupAlert();
     }
 
-    private View createView() {
+    private View createView(String errorContent) {
         View view = getLayoutInflater().inflate(R.layout.confirm_dialog, null);
         TextView contentView = (TextView) view.findViewById(R.id.content);
-        contentView.setText(mErrorContent);
+        contentView.setText(errorContent);
         return view;
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
index 1b5ffe1..8a887ee 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
@@ -32,6 +32,7 @@
 
 package com.android.bluetooth.opp;
 
+import android.bluetooth.AlertActivity;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -50,8 +51,6 @@
 import android.widget.Toast;
 
 import com.android.bluetooth.R;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
 
 /**
  * This class is designed to ask user to confirm if accept incoming file;
@@ -106,14 +105,11 @@
             return;
         }
 
-        // Set up the "dialog"
-        final AlertController.AlertParams p = mAlertParams;
-        p.mTitle = getString(R.string.incoming_file_confirm_content);
-        p.mView = createView();
-        p.mPositiveButtonText = getString(R.string.incoming_file_confirm_ok);
-        p.mPositiveButtonListener = this;
-        p.mNegativeButtonText = getString(R.string.incoming_file_confirm_cancel);
-        p.mNegativeButtonListener = this;
+        mAlertBuilder.setTitle(getString(R.string.incoming_file_confirm_content));
+        mAlertBuilder.setView(createView());
+        mAlertBuilder.setPositiveButton(R.string.incoming_file_confirm_ok, this);
+        mAlertBuilder.setNegativeButton(R.string.incoming_file_confirm_cancel, this);
+
         setupAlert();
         if (V) {
             Log.v(TAG, "mTimeout: " + mTimeout);
@@ -207,11 +203,14 @@
 
     private void onTimeout() {
         mTimeout = true;
-        mAlert.setTitle(
-                getString(R.string.incoming_file_confirm_timeout_content, mTransInfo.mDeviceName));
-        mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-        mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
-                .setText(getString(R.string.incoming_file_confirm_timeout_ok));
+
+        changeTitle(getString(
+                R.string.incoming_file_confirm_timeout_content,
+                mTransInfo.mDeviceName));
+        changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE);
+        changeButtonText(
+                DialogInterface.BUTTON_POSITIVE,
+                getString(R.string.incoming_file_confirm_timeout_ok));
 
         mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG),
                 DISMISS_TIMEOUT_DIALOG_VALUE);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
index a7c14ac..da3d100 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -33,6 +33,7 @@
 package com.android.bluetooth.opp;
 
 import android.app.NotificationManager;
+import android.bluetooth.AlertActivity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.DialogInterface;
@@ -49,8 +50,6 @@
 import android.widget.Toast;
 
 import com.android.bluetooth.R;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
 
 /**
  * Handle all transfer related dialogs: -Ongoing transfer -Receiving one file
@@ -79,8 +78,6 @@
 
     private TextView mPercentView;
 
-    private AlertController.AlertParams mPara;
-
     private View mView = null;
 
     private TextView mLine1View, mLine2View, mLine3View, mLine5View;
@@ -216,33 +213,23 @@
     }
 
     private void setUpDialog() {
-        // final AlertController.AlertParams p = mAlertParams;
-        mPara = mAlertParams;
-        mPara.mTitle = getString(R.string.download_title);
-
+        mAlertBuilder.setTitle(getString(R.string.download_title));
         if ((mWhichDialog == DIALOG_RECEIVE_ONGOING) || (mWhichDialog == DIALOG_SEND_ONGOING)) {
-            mPara.mPositiveButtonText = getString(R.string.download_ok);
-            mPara.mPositiveButtonListener = this;
-            mPara.mNegativeButtonText = getString(R.string.download_cancel);
-            mPara.mNegativeButtonListener = this;
+            mAlertBuilder.setPositiveButton(R.string.download_ok, this);
+            mAlertBuilder.setNegativeButton(R.string.download_cancel, this);
         } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) {
-            mPara.mPositiveButtonText = getString(R.string.download_succ_ok);
-            mPara.mPositiveButtonListener = this;
+            mAlertBuilder.setPositiveButton(R.string.download_succ_ok, this);
         } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) {
-            mPara.mIconAttrId = android.R.attr.alertDialogIcon;
-            mPara.mPositiveButtonText = getString(R.string.download_fail_ok);
-            mPara.mPositiveButtonListener = this;
+            mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
+            mAlertBuilder.setPositiveButton(R.string.download_fail_ok, this);
         } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) {
-            mPara.mPositiveButtonText = getString(R.string.upload_succ_ok);
-            mPara.mPositiveButtonListener = this;
+            mAlertBuilder.setPositiveButton(R.string.upload_succ_ok, this);
         } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
-            mPara.mIconAttrId = android.R.attr.alertDialogIcon;
-            mPara.mPositiveButtonText = getString(R.string.upload_fail_ok);
-            mPara.mPositiveButtonListener = this;
-            mPara.mNegativeButtonText = getString(R.string.upload_fail_cancel);
-            mPara.mNegativeButtonListener = this;
+            mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
+            mAlertBuilder.setPositiveButton(R.string.upload_fail_ok, this);
+            mAlertBuilder.setNegativeButton(R.string.upload_fail_cancel, this);
         }
-        mPara.mView = createView();
+        mAlertBuilder.setView(createView());
         setupAlert();
     }
 
@@ -471,24 +458,29 @@
      */
     private void updateButton() {
         if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) {
-            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
-                    .setText(getString(R.string.download_succ_ok));
+            changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE);
+            changeButtonText(
+                    DialogInterface.BUTTON_POSITIVE,
+                    getString(R.string.download_succ_ok));
         } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) {
-            mAlert.setIcon(mAlert.getIconAttributeResId(android.R.attr.alertDialogIcon));
-            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
-                    .setText(getString(R.string.download_fail_ok));
+            changeIconAttribute(android.R.attr.alertDialogIcon);
+            changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE);
+            changeButtonText(
+                    DialogInterface.BUTTON_POSITIVE,
+                    getString(R.string.download_fail_ok));
         } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) {
-            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
-                    .setText(getString(R.string.upload_succ_ok));
+            changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE);
+            changeButtonText(
+                    DialogInterface.BUTTON_POSITIVE,
+                    getString(R.string.upload_succ_ok));
         } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
-            mAlert.setIcon(mAlert.getIconAttributeResId(android.R.attr.alertDialogIcon));
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
-                    .setText(getString(R.string.upload_fail_ok));
-            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE)
-                    .setText(getString(R.string.upload_fail_cancel));
+            changeIconAttribute(android.R.attr.alertDialogIcon);
+            changeButtonText(
+                    DialogInterface.BUTTON_NEGATIVE,
+                    getString(R.string.upload_fail_cancel));
+            changeButtonText(
+                    DialogInterface.BUTTON_POSITIVE,
+                    getString(R.string.upload_fail_ok));
         }
     }
 
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
index 5468a76..3da71e0 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
@@ -32,6 +32,7 @@
 
 package com.android.bluetooth.pbap;
 
+import android.bluetooth.AlertActivity;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -47,13 +48,10 @@
 import android.text.TextWatcher;
 import android.util.Log;
 import android.view.View;
-import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
 
 import com.android.bluetooth.R;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
 
 /**
  * PbapActivity shows two dialogues: One for accepting incoming pbap request and
@@ -83,8 +81,6 @@
 
     private int mCurrentDialog;
 
-    private Button mOkButton;
-
     private boolean mTimeout = false;
 
     private static final int DISMISS_TIMEOUT_DIALOG = 0;
@@ -122,18 +118,14 @@
     }
 
     private void showPbapDialog(int id) {
-        final AlertController.AlertParams p = mAlertParams;
         switch (id) {
             case DIALOG_YES_NO_AUTH:
-                p.mTitle = getString(R.string.pbap_session_key_dialog_header);
-                p.mView = createView(DIALOG_YES_NO_AUTH);
-                p.mPositiveButtonText = getString(android.R.string.ok);
-                p.mPositiveButtonListener = this;
-                p.mNegativeButtonText = getString(android.R.string.cancel);
-                p.mNegativeButtonListener = this;
+                mAlertBuilder.setTitle(getString(R.string.pbap_session_key_dialog_header));
+                mAlertBuilder.setView(createView(DIALOG_YES_NO_AUTH));
+                mAlertBuilder.setPositiveButton(android.R.string.ok, this);
+                mAlertBuilder.setNegativeButton(android.R.string.cancel, this);
                 setupAlert();
-                mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
-                mOkButton.setEnabled(false);
+                changeButtonEnabled(DialogInterface.BUTTON_POSITIVE, false);
                 break;
             default:
                 break;
@@ -223,8 +215,8 @@
             mKeyView.setVisibility(View.GONE);
             mKeyView.clearFocus();
             mKeyView.removeTextChangedListener(this);
-            mOkButton.setEnabled(true);
-            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+            changeButtonEnabled(DialogInterface.BUTTON_POSITIVE, true);
+            changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE);
         }
 
         mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG),
@@ -271,7 +263,7 @@
     @Override
     public void afterTextChanged(android.text.Editable s) {
         if (s.length() > 0) {
-            mOkButton.setEnabled(true);
+            changeButtonEnabled(DialogInterface.BUTTON_POSITIVE, true);
         }
     }
 
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index e7dba2a..4ed499e 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -417,7 +417,15 @@
         }
     }
 
-    int getConnectionState(BluetoothDevice device) {
+    /**
+     * Get the current connection state of PBAP with the passed in device
+     *
+     * @param device is the device whose connection state to PBAP we are trying to get
+     * @return current connection state, one of {@link BluetoothProfile#STATE_DISCONNECTED},
+     * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
+     * {@link BluetoothProfile#STATE_DISCONNECTING}
+     */
+    public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mPbapStateMachineMap == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index f150cdd..16b02e1 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -317,7 +317,13 @@
         }
     }
 
-    boolean disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects the pbap client profile from the passed in device
+     *
+     * @param device is the device with which we will disconnect the pbap client profile
+     * @return true if we disconnected the pbap client profile, false otherwise
+     */
+    public boolean disconnect(BluetoothDevice device) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
         }
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index 78f4ad0..4492a45 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -39,9 +39,7 @@
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
-import com.android.bluetooth.avrcp.AvrcpTargetService;
 import com.android.bluetooth.btservice.AdapterService;
-import com.android.bluetooth.btservice.ServiceFactory;
 import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
@@ -78,8 +76,6 @@
     @Mock private AdapterService mAdapterService;
     @Mock private A2dpNativeInterface mA2dpNativeInterface;
     @Mock private DatabaseManager mDatabaseManager;
-    @Mock private AvrcpTargetService mAvrcpTargetService;
-    @Mock private ServiceFactory mFactory;
 
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
@@ -98,13 +94,11 @@
         TestUtils.setAdapterService(mAdapterService);
         doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
         doReturn(false).when(mAdapterService).isQuietModeEnabled();
-        doReturn(mAvrcpTargetService).when(mFactory).getAvrcpTargetService();
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
 
         startService();
         mA2dpService.mA2dpNativeInterface = mA2dpNativeInterface;
-        mA2dpService.mFactory = mFactory;
 
         // Override the timeout value to speed up the test
         A2dpStateMachine.sConnectTimeoutMs = TIMEOUT_MS;    // 1s
@@ -252,8 +246,6 @@
         });
         // Verify that setActiveDevice(null) was called during shutdown
         verify(mA2dpNativeInterface).setActiveDevice(null);
-        // Verify that storeVolumeForDevice(mTestDevice) was called during shutdown
-        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
         // Try to restart the service. Note: must be done on the main thread.
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             public void run() {
@@ -796,13 +788,11 @@
         Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
         Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, true));
         verify(mA2dpNativeInterface).setSilenceDevice(mTestDevice, true);
-        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
         Assert.assertNull(mA2dpService.getActiveDevice());
 
         // Test whether active device been resumeed after disable silence mode.
         Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, false));
         verify(mA2dpNativeInterface).setSilenceDevice(mTestDevice, false);
-        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
         Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
 
         // Test that active device should not be changed when silence a non-active device
@@ -810,13 +800,11 @@
         Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
         Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, true));
         verify(mA2dpNativeInterface).setSilenceDevice(otherDevice, true);
-        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
         Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
 
         // Test that active device should not be changed when another device exits silence mode
         Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, false));
         verify(mA2dpNativeInterface).setSilenceDevice(otherDevice, false);
-        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
         Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
     }
 
@@ -906,28 +894,6 @@
                 verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
     }
 
-    /**
-     * Test that volume level of previous active device will be stored after set active device.
-     */
-    @Test
-    public void testStoreVolumeAfterSetActiveDevice() {
-        BluetoothDevice otherDevice = mAdapter.getRemoteDevice("05:04:03:02:01:00");
-        connectDevice(otherDevice);
-        connectDevice(mTestDevice);
-        doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
-        doReturn(true).when(mA2dpNativeInterface).setActiveDevice(null);
-        Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
-
-        // Test volume stored for previous active device an adjust for current active device
-        Assert.assertTrue(mA2dpService.setActiveDevice(otherDevice));
-        verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
-        verify(mAvrcpTargetService).getRememberedVolumeForDevice(otherDevice);
-
-        // Test volume store for previous active device when set active device to null
-        Assert.assertTrue(mA2dpService.setActiveDevice(null));
-        verify(mAvrcpTargetService).storeVolumeForDevice(otherDevice);
-    }
-
     private void connectDevice(BluetoothDevice device) {
         connectDeviceWithCodecStatus(device, null);
     }
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
index f8ab6bb..0ca9c2a 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -151,6 +151,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());
     }
 
     /**
@@ -159,6 +163,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();
@@ -176,6 +185,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());
     }
 
     /**
@@ -186,6 +197,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();
@@ -203,6 +216,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());
     }
 
     /**