Adding support for Absolute Volume

Change-Id: I7bbc6f9296221ca219a50a5e377ebac9dcf5a407
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index d7d8cdbe..e7e4a0f 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -388,6 +388,66 @@
     }
 
     /**
+     * Checks if Avrcp device supports the absolute volume feature.
+     *
+     * @return true if device supports absolute volume
+     * @hide
+     */
+    public boolean isAvrcpAbsoluteVolumeSupported() {
+        if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.isAvrcpAbsoluteVolumeSupported();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Tells remote device to adjust volume. Only if absolute volume is supported.
+     *
+     * @param direction 1 to increase volume, or -1 to decrease volume
+     * @hide
+     */
+    public void adjustAvrcpAbsoluteVolume(int direction) {
+        if (DBG) Log.d(TAG, "adjustAvrcpAbsoluteVolume");
+        if (mService != null && isEnabled()) {
+            try {
+                mService.adjustAvrcpAbsoluteVolume(direction);
+                return;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in adjustAvrcpAbsoluteVolume()", e);
+                return;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+    }
+
+    /**
+     * Tells remote device to set an absolute volume. Only if absolute volume is supported
+     *
+     * @param volume Absolute volume to be set on AVRCP side
+     * @hide
+     */
+    public void setAvrcpAbsoluteVolume(int volume) {
+        if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
+        if (mService != null && isEnabled()) {
+            try {
+                mService.setAvrcpAbsoluteVolume(volume);
+                return;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
+                return;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+    }
+
+    /**
      * Check if A2DP profile is streaming music.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 1f10998..26ff9e27 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -32,5 +32,8 @@
     int getConnectionState(in BluetoothDevice device);
     boolean setPriority(in BluetoothDevice device, int priority);
     int getPriority(in BluetoothDevice device);
+    boolean isAvrcpAbsoluteVolumeSupported();
+    oneway void adjustAvrcpAbsoluteVolume(int direction);
+    oneway void setAvrcpAbsoluteVolume(int volume);
     boolean isA2dpPlaying(in BluetoothDevice device);
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 14cdbb7..ef02cfd 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2387,6 +2387,35 @@
         }
     }
 
+    /**
+     * @hide
+     * Notifies AudioService that it is connected to an A2DP device that supports absolute volume,
+     * so that AudioService can send volume change events to the A2DP device, rather than handling
+     * them.
+     */
+    public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
+        IAudioService service = getService();
+        try {
+            service.avrcpSupportsAbsoluteVolume(address, support);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in avrcpSupportsAbsoluteVolume", e);
+        }
+    }
+
+    /**
+     * @hide
+     * Notifies AudioService of the volume set on the A2DP device as a callback, so AudioService
+     * is able to update the UI.
+     */
+    public void avrcpUpdateVolume(int oldVolume, int volume) {
+        IAudioService service = getService();
+        try {
+            service.avrcpUpdateVolume(oldVolume, volume);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in avrcpUpdateVolume", e);
+        }
+    }
+
      /**
       * {@hide}
       */
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 290866e..470c571 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -460,6 +460,12 @@
 
     private final MediaFocusControl mMediaFocusControl;
 
+    // Reference to BluetoothA2dp to query for AbsoluteVolume.
+    private BluetoothA2dp mA2dp;
+    private final Object mA2dpAvrcpLock = new Object();
+    // If absolute volume is supported in AVRCP device
+    private boolean mAvrcpAbsVolSupported = false;
+
     ///////////////////////////////////////////////////////////////////////////
     // Construction
     ///////////////////////////////////////////////////////////////////////////
@@ -901,6 +907,15 @@
         int oldIndex = mStreamStates[streamType].getIndex(device);
 
         if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
+            // Check if volume update should be send to AVRCP
+            synchronized (mA2dpAvrcpLock) {
+                if (mA2dp != null && mAvrcpAbsVolSupported) {
+                    mA2dp.adjustAvrcpAbsoluteVolume(direction);
+                    return;
+                    // No need to send volume update, because we will update the volume with a
+                    // callback from Avrcp.
+                }
+            }
             if ((direction == AudioManager.ADJUST_RAISE) &&
                     !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
                 Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex);
@@ -998,6 +1013,15 @@
 
             index = rescaleIndex(index * 10, streamType, streamTypeAlias);
 
+            synchronized (mA2dpAvrcpLock) {
+                if (mA2dp != null && mAvrcpAbsVolSupported) {
+                    mA2dp.setAvrcpAbsoluteVolume(index);
+                    return;
+                    // No need to send volume update, because we will update the volume with a
+                    // callback from Avrcp.
+                }
+            }
+
             flags &= ~AudioManager.FLAG_FIXED_VOLUME;
             if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
                     ((device & mFixedVolumeDevices) != 0)) {
@@ -2268,21 +2292,23 @@
             List<BluetoothDevice> deviceList;
             switch(profile) {
             case BluetoothProfile.A2DP:
-                BluetoothA2dp a2dp = (BluetoothA2dp) proxy;
-                deviceList = a2dp.getConnectedDevices();
-                if (deviceList.size() > 0) {
-                    btDevice = deviceList.get(0);
-                    synchronized (mConnectedDevices) {
-                        int state = a2dp.getConnectionState(btDevice);
-                        int delay = checkSendBecomingNoisyIntent(
-                                                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                                                (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
-                        queueMsgUnderWakeLock(mAudioHandler,
-                                MSG_SET_A2DP_CONNECTION_STATE,
-                                state,
-                                0,
-                                btDevice,
-                                delay);
+                synchronized (mA2dpAvrcpLock) {
+                    mA2dp = (BluetoothA2dp) proxy;
+                    deviceList = mA2dp.getConnectedDevices();
+                    if (deviceList.size() > 0) {
+                        btDevice = deviceList.get(0);
+                        synchronized (mConnectedDevices) {
+                            int state = mA2dp.getConnectionState(btDevice);
+                            int delay = checkSendBecomingNoisyIntent(
+                                                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                                                    (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
+                            queueMsgUnderWakeLock(mAudioHandler,
+                                    MSG_SET_A2DP_CONNECTION_STATE,
+                                    state,
+                                    0,
+                                    btDevice,
+                                    delay);
+                        }
                     }
                 }
                 break;
@@ -2344,10 +2370,13 @@
         public void onServiceDisconnected(int profile) {
             switch(profile) {
             case BluetoothProfile.A2DP:
-                synchronized (mConnectedDevices) {
-                    if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) {
-                        makeA2dpDeviceUnavailableNow(
-                                mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+                synchronized (mA2dpAvrcpLock) {
+                    mA2dp = null;
+                    synchronized (mConnectedDevices) {
+                        if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) {
+                            makeA2dpDeviceUnavailableNow(
+                                    mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+                        }
                     }
                 }
                 break;
@@ -3697,6 +3726,7 @@
 
     private void onSetA2dpConnectionState(BluetoothDevice btDevice, int state)
     {
+        if (DEBUG_VOL) Log.d(TAG, "onSetA2dpConnectionState btDevice="+btDevice+" state="+state);
         if (btDevice == null) {
             return;
         }
@@ -3704,6 +3734,20 @@
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
+
+        // Disable absolute volume, if device is disconnected
+        synchronized (mA2dpAvrcpLock) {
+            if (state == BluetoothProfile.STATE_DISCONNECTED && mAvrcpAbsVolSupported) {
+                mAvrcpAbsVolSupported = false;
+                sendMsg(mAudioHandler,
+                        MSG_SET_DEVICE_VOLUME,
+                        SENDMSG_QUEUE,
+                        getDeviceForStream(AudioSystem.STREAM_MUSIC),
+                        0,
+                        mStreamStates[AudioSystem.STREAM_MUSIC],
+                        0);
+            }
+        }
         synchronized (mConnectedDevices) {
             boolean isConnected =
                 (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) &&
@@ -3754,6 +3798,31 @@
         }
     }
 
+    public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
+        // address is not used for now, but may be used when multiple a2dp devices are supported
+        synchronized (mA2dpAvrcpLock) {
+            mAvrcpAbsVolSupported = support;
+            if (support) {
+                VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
+                int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
+                streamState.setIndex(streamState.getMaxIndex(), device);
+                sendMsg(mAudioHandler,
+                        MSG_SET_DEVICE_VOLUME,
+                        SENDMSG_QUEUE,
+                        device,
+                        0,
+                        streamState,
+                        0);
+            }
+        }
+    }
+
+    public void avrcpUpdateVolume(int oldVolume, int volume) {
+        mStreamStates[AudioSystem.STREAM_MUSIC].
+                        setIndex(volume, getDeviceForStream(AudioSystem.STREAM_MUSIC));
+        sendVolumeUpdate(AudioSystem.STREAM_MUSIC, oldVolume, volume, AudioManager.FLAG_SHOW_UI);
+    }
+
     private boolean handleDeviceConnection(boolean connected, int device, String params) {
         synchronized (mConnectedDevices) {
             boolean isConnected = (mConnectedDevices.containsKey(device) &&
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index b4c8a04..903927b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -98,6 +98,10 @@
 
     oneway void reloadAudioSettings();
 
+    oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);
+
+    oneway void avrcpUpdateVolume(int oldVolume, int volume);
+
     void setSpeakerphoneOn(boolean on);
 
     boolean isSpeakerphoneOn();