diff options
author | Andre Eisenbach <eisenbach@google.com> | 2016-02-05 20:03:30 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2016-02-05 20:03:30 +0000 |
commit | 7a51f31646fa78a10bb89ff691a9e1fb8b8a1e34 (patch) | |
tree | 0660999e6253f564205fb381e429768eb7703a07 | |
parent | 26680aba171a83c5d8309246da2ce9aac6ccf677 (diff) | |
parent | 6b82837c0facc5f3c343a00f407abd6ee2312496 (diff) | |
download | android_packages_apps_Bluetooth-7a51f31646fa78a10bb89ff691a9e1fb8b8a1e34.tar.gz android_packages_apps_Bluetooth-7a51f31646fa78a10bb89ff691a9e1fb8b8a1e34.tar.bz2 android_packages_apps_Bluetooth-7a51f31646fa78a10bb89ff691a9e1fb8b8a1e34.zip |
Merge "Enhance AVRCP Absolute Volume control implementation" am: b8a26db0b7
am: 6b82837c0f
* commit '6b82837c0facc5f3c343a00f407abd6ee2312496':
Enhance AVRCP Absolute Volume control implementation
-rw-r--r-- | res/values/config.xml | 6 | ||||
-rwxr-xr-x | src/com/android/bluetooth/a2dp/A2dpService.java | 4 | ||||
-rwxr-xr-x | src/com/android/bluetooth/avrcp/Avrcp.java | 269 | ||||
-rw-r--r-- | src/com/android/bluetooth/btservice/BondStateMachine.java | 4 |
4 files changed, 250 insertions, 33 deletions
diff --git a/res/values/config.xml b/res/values/config.xml index d8709dc3b..408432a17 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -43,4 +43,10 @@ <integer name="gatt_low_power_max_interval">100</integer> <bool name="headset_client_initial_audio_route_allowed">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 e14302caf..a278927d3 100755 --- a/src/com/android/bluetooth/a2dp/A2dpService.java +++ b/src/com/android/bluetooth/a2dp/A2dpService.java @@ -213,6 +213,10 @@ public class A2dpService extends ProfileService { 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 58c4459b4..450574a92 100755 --- a/src/com/android/bluetooth/avrcp/Avrcp.java +++ b/src/com/android/bluetooth/avrcp/Avrcp.java @@ -23,6 +23,8 @@ import android.bluetooth.BluetoothA2dp; 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.MediaMetadataRetriever; @@ -43,6 +45,7 @@ import android.os.SystemClock; 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; @@ -52,6 +55,7 @@ import com.android.internal.util.StateMachine; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Set; /** @@ -61,6 +65,7 @@ import java.util.Set; 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; @@ -82,12 +87,23 @@ public final class Avrcp { 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; @@ -151,11 +167,17 @@ public final class Avrcp { 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; @@ -164,6 +186,10 @@ public final class Avrcp { 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() { @@ -195,6 +221,8 @@ public final class Avrcp { public void cleanup() { cleanupNative(); + if (mVolumeMapping != null) + mVolumeMapping.clear(); } private static class RemoteControllerWeak implements RemoteController.OnClientUpdateListener { @@ -281,7 +309,15 @@ public final class Avrcp { 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: @@ -319,47 +355,158 @@ public final class Avrcp { 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"); @@ -367,29 +514,50 @@ public final class Avrcp { 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; @@ -797,10 +965,13 @@ public final class Avrcp { } 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); } @@ -817,20 +988,50 @@ public final class Avrcp { } 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. */ @@ -856,14 +1057,16 @@ public final class Avrcp { 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/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java index 7a34ba94b..790597fb8 100644 --- a/src/com/android/bluetooth/btservice/BondStateMachine.java +++ b/src/com/android/bluetooth/btservice/BondStateMachine.java @@ -482,6 +482,10 @@ final class BondStateMachine extends StateMachine { 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) { |