Merge remote-tracking branch 'goog/security-aosp-mnc-mr1-release' into HEAD
diff --git a/jni/com_android_bluetooth_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp
index 5207f4d..ba1d78b 100644
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ b/jni/com_android_bluetooth_avrcp.cpp
@@ -72,8 +72,7 @@
     }
 
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
-    /* TODO: I think we leak the addr object, we should add a
-     * sCallbackEnv->DeleteLocalRef(addr) */
+    sCallbackEnv->DeleteLocalRef(addr);
 }
 
 static void btavrcp_get_play_status_callback() {
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index e2af3dd..ad5288e 100755
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -1129,6 +1129,27 @@
     return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static void interopDatabaseClearNative(JNIEnv *env, jobject obj) {
+    ALOGV("%s()", __FUNCTION__);
+    if (!sBluetoothInterface) return;
+    sBluetoothInterface->interop_database_clear();
+}
+
+static void interopDatabaseAddNative(JNIEnv *env, jobject obj, int feature,
+                                      jbyteArray address, int length) {
+    ALOGV("%s()", __FUNCTION__);
+    if (!sBluetoothInterface) return;
+
+    jbyte *addr = env->GetByteArrayElements(address, NULL);
+    if (addr == NULL) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    sBluetoothInterface->interop_database_add(feature, (bt_bdaddr_t *)addr, length);
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
 static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
     {"classInitNative", "()V", (void *) classInitNative},
@@ -1157,7 +1178,9 @@
     {"alarmFiredNative", "()V", (void *) alarmFiredNative},
     {"readEnergyInfo", "()I", (void*) readEnergyInfo},
     {"dumpNative", "(Ljava/io/FileDescriptor;)V", (void*) dumpNative},
-    {"factoryResetNative", "()Z", (void*)factoryResetNative}
+    {"factoryResetNative", "()Z", (void*)factoryResetNative},
+    {"interopDatabaseClearNative", "()V", (void*) interopDatabaseClearNative},
+    {"interopDatabaseAddNative", "(I[BI)V", (void*) interopDatabaseAddNative}
 };
 
 int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env)
diff --git a/res/values/config.xml b/res/values/config.xml
index 1684183..b32d585 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -33,4 +33,9 @@
          fire Bluetooth LE scan result callbacks in addition to having one
          of the location permissions. -->
     <bool name="strict_location_check">true</bool>
+
+    <!-- For AVRCP absolute volume feature. If the threshold is non-zero,
+         restrict the initial volume to the threshold.
+         Valid value is 1-14, and recommended value is 8 -->
+    <integer name="a2dp_absolute_volume_initial_threshold">8</integer>
 </resources>
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index e14302c..a278927 100755
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -213,6 +213,10 @@
         mAvrcp.setA2dpAudioState(state);
     }
 
+    public void resetAvrcpBlacklist(BluetoothDevice device) {
+        mAvrcp.resetBlackList(device.getAddress());
+    }
+
     synchronized boolean isA2dpPlaying(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM,
                                        "Need BLUETOOTH permission");
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index abc0b84..7d14037 100755
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -24,6 +24,8 @@
 import android.bluetooth.BluetoothAvrcp;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.media.AudioManager;
 import android.media.IRemoteControlDisplay;
@@ -45,6 +47,7 @@
 import android.util.Log;
 import android.view.KeyEvent;
 
+import com.android.bluetooth.R;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.Utils;
@@ -54,6 +57,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 /**
@@ -63,6 +67,7 @@
 public final class Avrcp {
     private static final boolean DEBUG = false;
     private static final String TAG = "Avrcp";
+    private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist";
 
     private Context mContext;
     private final AudioManager mAudioManager;
@@ -84,12 +89,23 @@
     private long mPrevPosMs;
     private long mSkipStartTime;
     private int mFeatures;
-    private int mAbsoluteVolume;
-    private int mLastSetVolume;
+    private int mRemoteVolume;
+    private int mLastRemoteVolume;
+    private int mInitialRemoteVolume;
+
+    /* Local volume in audio index 0-15 */
+    private int mLocalVolume;
+    private int mLastLocalVolume;
+    private int mAbsVolThreshold;
+
+    private String mAddress;
+    private HashMap<Integer, Integer> mVolumeMapping;
+
     private int mLastDirection;
     private final int mVolumeStep;
     private final int mAudioStreamMax;
-    private boolean mVolCmdInProgress;
+    private boolean mVolCmdAdjustInProgress;
+    private boolean mVolCmdSetInProgress;
     private int mAbsVolRetryTimes;
     private int mSkipAmount;
 
@@ -153,11 +169,17 @@
         mPlaybackIntervalMs = 0L;
         mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
         mFeatures = 0;
-        mAbsoluteVolume = -1;
-        mLastSetVolume = -1;
+        mRemoteVolume = -1;
+        mInitialRemoteVolume = -1;
+        mLastRemoteVolume = -1;
         mLastDirection = 0;
-        mVolCmdInProgress = false;
+        mVolCmdAdjustInProgress = false;
+        mVolCmdSetInProgress = false;
         mAbsVolRetryTimes = 0;
+        mLocalVolume = -1;
+        mLastLocalVolume = -1;
+        mAbsVolThreshold = 0;
+        mVolumeMapping = new HashMap<Integer, Integer>();
 
         mContext = context;
 
@@ -166,6 +188,10 @@
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
         mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax);
+        Resources resources = context.getResources();
+        if (resources != null) {
+            mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
+        }
     }
 
     private void start() {
@@ -197,6 +223,8 @@
 
     public void cleanup() {
         cleanupNative();
+        if (mVolumeMapping != null)
+            mVolumeMapping.clear();
     }
 
     private static class RemoteControllerWeak implements RemoteController.OnClientUpdateListener {
@@ -283,7 +311,15 @@
                 if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+
                                                              ", features="+msg.arg1);
                 mFeatures = msg.arg1;
+                mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address);
                 mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
+                mLastLocalVolume = -1;
+                mRemoteVolume = -1;
+                mLocalVolume = -1;
+                mInitialRemoteVolume = -1;
+                mAddress = address;
+                if (mVolumeMapping != null)
+                    mVolumeMapping.clear();
                 break;
 
             case MESSAGE_GET_PLAY_STATUS:
@@ -321,47 +357,158 @@
                 break;
 
             case MESSAGE_VOLUME_CHANGED:
+                if (!isAbsoluteVolumeSupported()) {
+                    if (DEBUG) Log.v(TAG, "ignore MESSAGE_VOLUME_CHANGED");
+                    break;
+                }
+
                 if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f)
                                                         + " ctype=" + msg.arg2);
 
+
+                boolean volAdj = false;
                 if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
-                    if (mVolCmdInProgress == false) {
+                    if (mVolCmdAdjustInProgress == false && mVolCmdSetInProgress == false) {
                         Log.e(TAG, "Unsolicited response, ignored");
                         break;
                     }
                     removeMessages(MESSAGE_ABS_VOL_TIMEOUT);
-                    mVolCmdInProgress = false;
+
+                    volAdj = mVolCmdAdjustInProgress;
+                    mVolCmdAdjustInProgress = false;
+                    mVolCmdSetInProgress = false;
                     mAbsVolRetryTimes = 0;
                 }
-                if (mAbsoluteVolume != msg.arg1 && (msg.arg2 == AVRC_RSP_ACCEPT ||
+
+                byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
+                // convert remote volume to local volume
+                int volIndex = convertToAudioStreamVolume(absVol);
+                if (mInitialRemoteVolume == -1) {
+                    mInitialRemoteVolume = absVol;
+                    if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax && volIndex > mAbsVolThreshold) {
+                        if (DEBUG) Log.v(TAG, "remote inital volume too high " + volIndex + ">" + mAbsVolThreshold);
+                        Message msg1 = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0);
+                        mHandler.sendMessage(msg1);
+                        mRemoteVolume = absVol;
+                        mLocalVolume = volIndex;
+                        break;
+                    }
+                }
+
+                if (mLocalVolume != volIndex &&
+                                                   (msg.arg2 == AVRC_RSP_ACCEPT ||
                                                     msg.arg2 == AVRC_RSP_CHANGED ||
                                                     msg.arg2 == AVRC_RSP_INTERIM)) {
-                    byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
-                    notifyVolumeChanged(absVol);
-                    mAbsoluteVolume = absVol;
+                    /* If the volume has successfully changed */
+                    mLocalVolume = volIndex;
+                    if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) {
+                        if (mLastLocalVolume != volIndex) {
+                            /* remote volume changed more than requested due to
+                             * local and remote has different volume steps */
+                            if (DEBUG) Log.d(TAG, "Remote returned volume does not match desired volume "
+                                + mLastLocalVolume + " vs "
+                                + volIndex);
+                            mLastLocalVolume = mLocalVolume;
+                        }
+                    }
+                    // remember the remote volume value, as it's the one supported by remote
+                    if (volAdj) {
+                        synchronized (mVolumeMapping) {
+                            mVolumeMapping.put(volIndex, (int)absVol);
+                            if (DEBUG) Log.v(TAG, "remember volume mapping " +volIndex+ "-"+absVol);
+                        }
+                    }
+
+                    notifyVolumeChanged(mLocalVolume);
+                    mRemoteVolume = absVol;
                     long pecentVolChanged = ((long)absVol * 100) / 0x7f;
                     Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
                 } else if (msg.arg2 == AVRC_RSP_REJ) {
                     Log.e(TAG, "setAbsoluteVolume call rejected");
+                } else if (volAdj && mLastRemoteVolume > 0 && mLastRemoteVolume < AVRCP_MAX_VOL &&
+                           mLocalVolume == volIndex &&
+                                                   (msg.arg2 == AVRC_RSP_ACCEPT )) {
+                    /* oops, the volume is still same, remote does not like the value
+                     * retry a volume one step up/down */
+                    if (DEBUG) Log.d(TAG, "Remote device didn't tune volume, let's try one more step.");
+                    int retry_volume = Math.min(AVRCP_MAX_VOL,
+                            Math.max(0, mLastRemoteVolume + mLastDirection));
+                    if (setVolumeNative(retry_volume)) {
+                        mLastRemoteVolume = retry_volume;
+                        sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
+                                           CMD_TIMEOUT_DELAY);
+                        mVolCmdAdjustInProgress = true;
+                    }
                 }
                 break;
 
             case MESSAGE_ADJUST_VOLUME:
+                if (!isAbsoluteVolumeSupported()) {
+                    if (DEBUG) Log.v(TAG, "ignore MESSAGE_ADJUST_VOLUME");
+                    break;
+                }
+
                 if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1);
-                if (mVolCmdInProgress) {
+
+                if (mVolCmdAdjustInProgress || mVolCmdSetInProgress) {
                     if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
                     break;
                 }
+
+                // Remote device didn't set initial volume. Let's black list it
+                if (mInitialRemoteVolume == -1) {
+                    Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it.");
+                    blackListCurrentDevice();
+                    break;
+                }
+
                 // Wait on verification on volume from device, before changing the volume.
-                if (mAbsoluteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
-                    int setVol = Math.min(AVRCP_MAX_VOL,
-                                 Math.max(0, mAbsoluteVolume + msg.arg1*mVolumeStep));
+                if (mRemoteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
+                    int setVol = -1;
+                    int targetVolIndex = -1;
+                    if (mLocalVolume == 0 && msg.arg1 == -1) {
+                        if (DEBUG) Log.w(TAG, "No need to Vol down from 0.");
+                        break;
+                    }
+                    if (mLocalVolume == mAudioStreamMax && msg.arg1 == 1) {
+                        if (DEBUG) Log.w(TAG, "No need to Vol up from max.");
+                        break;
+                    }
+
+                    targetVolIndex = mLocalVolume + msg.arg1;
+                    if (DEBUG) Log.d(TAG, "Adjusting volume to  " + targetVolIndex);
+
+                    Integer i;
+                    synchronized (mVolumeMapping) {
+                        i = mVolumeMapping.get(targetVolIndex);
+                    }
+
+                    if (i != null) {
+                        /* if we already know this volume mapping, use it */
+                        setVol = i.byteValue();
+                        if (setVol == mRemoteVolume) {
+                            if (DEBUG) Log.d(TAG, "got same volume from mapping for " + targetVolIndex + ", ignore.");
+                            setVol = -1;
+                        }
+                        if (DEBUG) Log.d(TAG, "set volume from mapping " + targetVolIndex + "-" + setVol);
+                    }
+
+                    if (setVol == -1) {
+                        /* otherwise use phone steps */
+                        setVol = Math.min(AVRCP_MAX_VOL,
+                                 convertToAvrcpVolume(Math.max(0, targetVolIndex)));
+                        if (DEBUG) Log.d(TAG, "set volume from local volume "+ targetVolIndex+"-"+ setVol);
+                    }
+
                     if (setVolumeNative(setVol)) {
                         sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
                                            CMD_TIMEOUT_DELAY);
-                        mVolCmdInProgress = true;
+                        mVolCmdAdjustInProgress = true;
                         mLastDirection = msg.arg1;
-                        mLastSetVolume = setVol;
+                        mLastRemoteVolume = setVol;
+                        mLastLocalVolume = targetVolIndex;
+                    } else {
+                         if (DEBUG) Log.d(TAG, "setVolumeNative failed");
                     }
                 } else {
                     Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME");
@@ -369,29 +516,50 @@
                 break;
 
             case MESSAGE_SET_ABSOLUTE_VOLUME:
+                if (!isAbsoluteVolumeSupported()) {
+                    if (DEBUG) Log.v(TAG, "ignore MESSAGE_SET_ABSOLUTE_VOLUME");
+                    break;
+                }
+
                 if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME");
-                if (mVolCmdInProgress) {
+
+                if (mVolCmdSetInProgress || mVolCmdAdjustInProgress) {
                     if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
                     break;
                 }
-                if (setVolumeNative(msg.arg1)) {
+
+                // Remote device didn't set initial volume. Let's black list it
+                if (mInitialRemoteVolume == -1) {
+                    if (DEBUG) Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it.");
+                    blackListCurrentDevice();
+                    break;
+                }
+
+                int avrcpVolume = convertToAvrcpVolume(msg.arg1);
+                avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
+                if (DEBUG) Log.d(TAG, "Setting volume to " + msg.arg1+"-"+avrcpVolume);
+                if (setVolumeNative(avrcpVolume)) {
                     sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
-                    mVolCmdInProgress = true;
-                    mLastSetVolume = msg.arg1;
+                    mVolCmdSetInProgress = true;
+                    mLastRemoteVolume = avrcpVolume;
+                    mLastLocalVolume = msg.arg1;
+                } else {
+                     if (DEBUG) Log.d(TAG, "setVolumeNative failed");
                 }
                 break;
 
             case MESSAGE_ABS_VOL_TIMEOUT:
                 if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
-                mVolCmdInProgress = false;
+                mVolCmdAdjustInProgress = false;
+                mVolCmdSetInProgress = false;
                 if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
                     mAbsVolRetryTimes = 0;
                 } else {
                     mAbsVolRetryTimes += 1;
-                    if (setVolumeNative(mLastSetVolume)) {
+                    if (setVolumeNative(mLastRemoteVolume)) {
                         sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
                                            CMD_TIMEOUT_DELAY);
-                        mVolCmdInProgress = true;
+                        mVolCmdSetInProgress = true;
                     }
                 }
                 break;
@@ -799,10 +967,13 @@
     }
 
     public void setAbsoluteVolume(int volume) {
-        int avrcpVolume = convertToAvrcpVolume(volume);
-        avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
+        if (volume == mLocalVolume) {
+            if (DEBUG) Log.v(TAG, "setAbsoluteVolume is setting same index, ignore "+volume);
+            return;
+        }
+
         mHandler.removeMessages(MESSAGE_ADJUST_VOLUME);
-        Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, avrcpVolume, 0);
+        Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, volume, 0);
         mHandler.sendMessage(msg);
     }
 
@@ -819,20 +990,50 @@
     }
 
     private void notifyVolumeChanged(int volume) {
-        volume = convertToAudioStreamVolume(volume);
         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
                       AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
     }
 
     private int convertToAudioStreamVolume(int volume) {
         // Rescale volume to match AudioSystem's volume
-        return (int) Math.round((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
+        return (int) Math.floor((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
     }
 
     private int convertToAvrcpVolume(int volume) {
         return (int) Math.ceil((double) volume*AVRCP_MAX_VOL/mAudioStreamMax);
     }
 
+    private void blackListCurrentDevice() {
+        mFeatures &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
+        mAudioManager.avrcpSupportsAbsoluteVolume(mAddress, isAbsoluteVolumeSupported());
+
+        SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+        editor.putBoolean(mAddress, true);
+        editor.commit();
+    }
+
+    private int modifyRcFeatureFromBlacklist(int feature, String address) {
+        SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+                Context.MODE_PRIVATE);
+        if (!pref.contains(address)) {
+            return feature;
+        }
+        if (pref.getBoolean(address, false)) {
+            feature &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
+        }
+        return feature;
+    }
+
+    public void resetBlackList(String address) {
+        SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+        editor.remove(address);
+        editor.commit();
+    }
+
     /**
      * This is called from A2dpStateMachine to set A2dp audio state.
      */
@@ -858,14 +1059,16 @@
         ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
         ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime);
         ProfileService.println(sb, "mFeatures: " + mFeatures);
-        ProfileService.println(sb, "mAbsoluteVolume: " + mAbsoluteVolume);
-        ProfileService.println(sb, "mLastSetVolume: " + mLastSetVolume);
+        ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume);
+        ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume);
         ProfileService.println(sb, "mLastDirection: " + mLastDirection);
         ProfileService.println(sb, "mVolumeStep: " + mVolumeStep);
         ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax);
-        ProfileService.println(sb, "mVolCmdInProgress: " + mVolCmdInProgress);
+        ProfileService.println(sb, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress);
+        ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
         ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
         ProfileService.println(sb, "mSkipAmount: " + mSkipAmount);
+        ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
     }
 
     // Do not modify without updating the HAL bt_rc.h files.
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 8f72312..3ac242c 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -360,6 +360,8 @@
             mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BREDR_STOPPED));
 
         } else if (isTurningOn) {
+            updateInteropDatabase();
+
             //Process start pending
             //Check if all services are started if so, update state
             synchronized (mProfileServicesState) {
@@ -385,6 +387,59 @@
         }
     }
 
+    private void updateInteropDatabase() {
+        interopDatabaseClearNative();
+
+        String interop_string = Settings.Global.getString(getContentResolver(),
+                                            Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST);
+        if (interop_string == null) return;
+        Log.d(TAG, "updateInteropDatabase: [" + interop_string + "]");
+
+        String[] entries = interop_string.split(";");
+        for (String entry : entries) {
+            String[] tokens = entry.split(",");
+            if (tokens.length != 2) continue;
+
+            // Get feature
+            int feature = 0;
+            try {
+                feature = Integer.parseInt(tokens[1]);
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "updateInteropDatabase: Invalid feature '" + tokens[1] + "'");
+                continue;
+            }
+
+            // Get address bytes and length
+            int length = (tokens[0].length() + 1) / 3;
+            if (length < 1 || length > 6) {
+                Log.e(TAG, "updateInteropDatabase: Malformed address string '" + tokens[0] + "'");
+                continue;
+            }
+
+            byte[] addr = new byte[6];
+            int offset = 0;
+            for (int i = 0; i < tokens[0].length(); ) {
+                if (tokens[0].charAt(i) == ':') {
+                    i += 1;
+                } else {
+                    try {
+                        addr[offset++] = (byte) Integer.parseInt(tokens[0].substring(i, i + 2), 16);
+                    } catch (NumberFormatException e) {
+                        offset = 0;
+                        break;
+                    }
+                    i += 2;
+                }
+            }
+
+            // Check if address was parsed ok, otherwise, move on...
+            if (offset == 0) continue;
+
+            // Add entry
+            interopDatabaseAddNative(feature, addr, length);
+        }
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -2139,6 +2194,9 @@
     private native void alarmFiredNative();
     private native void dumpNative(FileDescriptor fd);
 
+    private native void interopDatabaseClearNative();
+    private native void interopDatabaseAddNative(int feature, byte[] address, int length);
+
     protected void finalize() {
         cleanup();
         if (TRACE_REF) {
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 7bed94a..b4241c1 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -462,6 +462,10 @@
             a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
         if(headsetService != null)
             headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
+
+        // Clear Absolute Volume black list
+        if(a2dpService != null)
+            a2dpService.resetAvrcpBlacklist(device);
     }
 
     private void infoLog(String msg) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index 66ceecc..f10c4ec 100755
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -394,15 +394,16 @@
     static public byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
             throws UnsupportedEncodingException {
 
-        byte[] utf8Bytes = null;
+        byte[] utf8Bytes = new byte[utf8String.length() + 1];
         try {
-            utf8Bytes = utf8String.getBytes("UTF-8");
+            System.arraycopy(utf8String.getBytes("UTF-8"), 0,
+                             utf8Bytes, 0, utf8String.length());
         } catch (UnsupportedEncodingException e) {
             Log.e(TAG,"truncateUtf8StringToBytearray: getBytes exception ", e);
             throw e;
         }
 
-        if (utf8Bytes.length > (maxLength - 1)) {
+        if (utf8Bytes.length > maxLength) {
             /* if 'continuation' byte is in place 200,
              * then strip previous bytes until utf-8 start byte is found */
             if ( (utf8Bytes[maxLength - 1] & 0xC0) == 0x80 ) {