Merge "Remove media players if their backing session dissappears"
diff --git a/res/raw/silent.wav b/res/raw/silent.wav
new file mode 100755
index 0000000..d6d93ef
--- /dev/null
+++ b/res/raw/silent.wav
Binary files differ
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 4de89df..918c2ce 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -17,13 +17,17 @@
package com.android.bluetooth.a2dp;
import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
+import android.util.Log;
import com.android.bluetooth.R;
+import java.util.Arrays;
+import java.util.Objects;
/*
* A2DP Codec Configuration setup.
*/
@@ -52,13 +56,47 @@
}
void setCodecConfigPreference(BluetoothDevice device,
+ BluetoothCodecStatus codecStatus,
BluetoothCodecConfig codecConfig) {
+ Objects.requireNonNull(codecStatus);
+
+ // Check whether the codecConfig is selectable for this Bluetooth device.
+ BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities();
+ if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec ->
+ codec.isMandatoryCodec())) {
+ // Do not set codec preference to native if the selectableCodecs not contain mandatory
+ // codec. The reason could be remote codec negotiation is not completed yet.
+ Log.w(TAG, "Cannot find mandatory codec in selectableCodecs.");
+ return;
+ }
+ if (!isCodecConfigSelectable(codecConfig, selectableCodecs)) {
+ Log.w(TAG, "Codec is not selectable: " + codecConfig);
+ return;
+ }
+
+ // Check whether the codecConfig would change current codec config.
+ int prioritizedCodecType = getPrioitizedCodecType(codecConfig, selectableCodecs);
+ BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
+ if (prioritizedCodecType == currentCodecConfig.getCodecType()
+ && (currentCodecConfig.getCodecType() != codecConfig.getCodecType()
+ || currentCodecConfig.sameAudioFeedingParameters(codecConfig))) {
+ // Same codec with same parameters, no need to send this request to native.
+ Log.i(TAG, "setCodecConfigPreference: codec not changed.");
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
codecConfigArray[0] = codecConfig;
mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
}
- void enableOptionalCodecs(BluetoothDevice device) {
+ void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+ if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) {
+ Log.i(TAG, "enableOptionalCodecs: already using optional codec: "
+ + currentCodecConfig.getCodecType());
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
if (codecConfigArray == null) {
return;
@@ -75,7 +113,12 @@
mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
}
- void disableOptionalCodecs(BluetoothDevice device) {
+ void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+ if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) {
+ Log.i(TAG, "disableOptionalCodecs: already using mandatory codec");
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
if (codecConfigArray == null) {
return;
@@ -92,6 +135,35 @@
mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
}
+ // Get the codec type of the highest priority of selectableCodecs and codecConfig.
+ private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig,
+ BluetoothCodecConfig[] selectableCodecs) {
+ BluetoothCodecConfig prioritizedCodecConfig = codecConfig;
+ for (BluetoothCodecConfig config : selectableCodecs) {
+ if (prioritizedCodecConfig == null) {
+ prioritizedCodecConfig = config;
+ }
+ if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) {
+ prioritizedCodecConfig = config;
+ }
+ }
+ return prioritizedCodecConfig.getCodecType();
+ }
+
+ // Check whether the codecConfig is selectable
+ private static boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig,
+ BluetoothCodecConfig[] selectableCodecs) {
+ for (BluetoothCodecConfig config : selectableCodecs) {
+ if (codecConfig.getCodecType() == config.getCodecType()
+ && (codecConfig.getSampleRate() & config.getSampleRate()) != 0
+ && (codecConfig.getBitsPerSample() & config.getBitsPerSample()) != 0
+ && (codecConfig.getChannelMode() & config.getChannelMode()) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// Assign the A2DP Source codec config priorities
private BluetoothCodecConfig[] assignCodecConfigPriorities() {
Resources resources = mContext.getResources();
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 810a7c0..085bbb1 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -719,7 +719,17 @@
Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
return;
}
- mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "Cannot set codec config preference: not supported");
+ return;
+ }
+
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "Codec status is null on " + device);
+ return;
+ }
+ mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig);
}
/**
@@ -741,7 +751,16 @@
Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
return;
}
- mA2dpCodecConfig.enableOptionalCodecs(device);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "Cannot enable optional codecs: not supported");
+ return;
+ }
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "Cannot enable optional codecs: codec status is null");
+ return;
+ }
+ mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
}
/**
@@ -763,7 +782,16 @@
Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
return;
}
- mA2dpCodecConfig.disableOptionalCodecs(device);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "Cannot disable optional codecs: not supported");
+ return;
+ }
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "Cannot disable optional codecs: codec status is null");
+ return;
+ }
+ mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
}
public int getSupportsOptionalCodecs(BluetoothDevice device) {
@@ -835,7 +863,8 @@
* @param sameAudioFeedingParameters if true the audio feeding parameters
* haven't been changed
*/
- void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
+ @VisibleForTesting
+ public void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
boolean sameAudioFeedingParameters) {
// Log codec config and capability metrics
BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
@@ -987,9 +1016,17 @@
}
}
- private void updateOptionalCodecsSupport(BluetoothDevice device) {
+
+ /**
+ * Update and initiate optional codec status change to native.
+ *
+ * @param device the device to change optional codec status
+ */
+ @VisibleForTesting
+ public void updateOptionalCodecsSupport(BluetoothDevice device) {
int previousSupport = getSupportsOptionalCodecs(device);
boolean supportsOptional = false;
+ boolean hasMandatoryCodec = false;
synchronized (mStateMachines) {
A2dpStateMachine sm = mStateMachines.get(device);
@@ -999,13 +1036,22 @@
BluetoothCodecStatus codecStatus = sm.getCodecStatus();
if (codecStatus != null) {
for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
- if (!config.isMandatoryCodec()) {
+ if (config.isMandatoryCodec()) {
+ hasMandatoryCodec = true;
+ } else {
supportsOptional = true;
- break;
}
}
}
}
+ if (!hasMandatoryCodec) {
+ // Mandatory codec(SBC) is not selectable. It could be caused by the remote device
+ // select codec before native finish get codec capabilities. Stop use this codec
+ // status as the reference to support/enable optional codecs.
+ Log.i(TAG, "updateOptionalCodecsSupport: Mandatory codec is not selectable.");
+ return;
+ }
+
if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
|| supportsOptional != (previousSupport
== BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
@@ -1034,10 +1080,6 @@
}
synchronized (mStateMachines) {
if (toState == BluetoothProfile.STATE_CONNECTED) {
- // Each time a device connects, we want to re-check if it supports optional
- // codecs (perhaps it's had a firmware update, etc.) and save that state if
- // it differs from what we had saved before.
- updateOptionalCodecsSupport(device);
MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
}
// Set the active device if only one connected device is supported and it was connected
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 9c8a221..e0df8b2 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -89,7 +89,8 @@
private A2dpService mA2dpService;
private A2dpNativeInterface mA2dpNativeInterface;
- private boolean mA2dpOffloadEnabled = false;
+ @VisibleForTesting
+ boolean mA2dpOffloadEnabled = false;
private final BluetoothDevice mDevice;
private boolean mIsPlaying = false;
private BluetoothCodecStatus mCodecStatus;
@@ -467,6 +468,10 @@
removeDeferredMessages(CONNECT);
+ // Each time a device connects, we want to re-check if it supports optional
+ // codecs (perhaps it's had a firmware update, etc.) and save that state if
+ // it differs from what we had saved before.
+ mA2dpService.updateOptionalCodecsSupport(mDevice);
broadcastConnectionState(mConnectionState, mLastConnectionState);
// Upon connected, the audio starts out as stopped
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
@@ -608,8 +613,11 @@
}
// NOTE: This event is processed in any state
- private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
+ @VisibleForTesting
+ void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
BluetoothCodecConfig prevCodecConfig = null;
+ BluetoothCodecStatus prevCodecStatus = mCodecStatus;
+
synchronized (this) {
if (mCodecStatus != null) {
prevCodecConfig = mCodecStatus.getCodecConfig();
@@ -630,6 +638,12 @@
}
}
+ if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) {
+ // Remote selectable codec could be changed if codec config changed
+ // in connected state, we need to re-check optional codec status
+ // for this codec change event.
+ mA2dpService.updateOptionalCodecsSupport(mDevice);
+ }
if (mA2dpOffloadEnabled) {
boolean update = false;
BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -696,6 +710,16 @@
return builder.toString();
}
+ private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
+ BluetoothCodecStatus newCodecStatus) {
+ if (prevCodecStatus == null) {
+ return false;
+ }
+ return BluetoothCodecStatus.sameCapabilities(
+ prevCodecStatus.getCodecsSelectableCapabilities(),
+ newCodecStatus.getCodecsSelectableCapabilities());
+ }
+
private static String messageWhatToString(int what) {
switch (what) {
case CONNECT:
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index e3cc2db..8e1af83 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -53,7 +53,8 @@
mId = INVALID_ID;
//Set Default Actions in case Player data isn't available.
mAvailableActions = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
- | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
+ | PlaybackState.ACTION_STOP;
}
AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) {
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 323dbfb..4dfb8d4 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -23,7 +23,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.MediaMetadata;
+import android.media.MediaPlayer;
import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.session.MediaController;
@@ -95,8 +98,16 @@
"com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
+
+ // In order to be considered as an audio source capable of receiving media key events (In the
+ // eyes of MediaSessionService), we need an active MediaPlayer in addition to a MediaSession.
+ // Because of this, the media player below plays an incredibly short, silent audio sample so
+ // that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the current
+ // active player and send us media events. This is a restriction currently imposed by the media
+ // framework code and could be reconsidered in the future.
private MediaSession mSession;
private MediaMetadata mA2dpMetadata;
+ private MediaPlayer mMediaPlayer;
private AvrcpControllerService mAvrcpCtrlSrvc;
private boolean mBrowseConnected = false;
@@ -158,23 +169,37 @@
}
}
+ /**
+ * Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and
+ * MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
+ */
@Override
public void onCreate() {
if (DBG) Log.d(TAG, "onCreate");
super.onCreate();
+ // Create and configure the MediaSession
mSession = new MediaSession(this, TAG);
- setSessionToken(mSession.getSessionToken());
mSession.setCallback(mSessionCallbacks);
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
- mSession.setActive(true);
mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
mSession.setQueue(mMediaQueue);
+
+ // Create and setup the MediaPlayer
+ initMediaPlayer();
+
+ // Associate the held MediaSession with this browser and activate it
+ setSessionToken(mSession.getSessionToken());
+ mSession.setActive(true);
+
+ // Internal handler to process events and requests
mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
+ // Set the initial Media state (sets current playback state and media meta data)
refreshInitialPlayingState();
+ // Set up communication with the controller service
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
@@ -185,19 +210,82 @@
synchronized (this) {
mParentIdToRequestMap.clear();
}
- setBluetoothMediaBrowserService(this);
+ setBluetoothMediaBrowserService(this);
}
+ /**
+ * Clean up this instance in the reverse order that we created it.
+ */
@Override
public void onDestroy() {
if (DBG) Log.d(TAG, "onDestroy");
setBluetoothMediaBrowserService(null);
- mSession.release();
unregisterReceiver(mBtReceiver);
+ destroyMediaPlayer();
+ mSession.release();
super.onDestroy();
}
+ /**
+ * Initializes the silent MediaPlayer object which aids in receiving media key focus.
+ *
+ * The created MediaPlayer is already prepared and will release and stop itself on error. All
+ * you need to do is start() it.
+ */
+ private void initMediaPlayer() {
+ if (DBG) Log.d(TAG, "initMediaPlayer()");
+
+ // Parameters for create
+ AudioAttributes attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+ AudioManager am = getSystemService(AudioManager.class);
+
+ // Create our player object. Returns a prepared player on success, null on failure
+ mMediaPlayer = MediaPlayer.create(this, R.raw.silent, attrs, am.generateAudioSessionId());
+ if (mMediaPlayer == null) {
+ Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+ return;
+ }
+
+ // Set other player attributes
+ mMediaPlayer.setLooping(false);
+ mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+ Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+ destroyMediaPlayer();
+ return false;
+ });
+ }
+
+ /**
+ * Safely tears down our local MediaPlayer
+ */
+ private void destroyMediaPlayer() {
+ if (DBG) Log.d(TAG, "destroyMediaPlayer()");
+ if (mMediaPlayer == null) {
+ return;
+ }
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+
+ /**
+ * Uses the internal MediaPlayer to play a silent, short audio sample so that AudioService will
+ * treat us as the active MediaSession/MediaPlayer combo and properly route us media key events.
+ *
+ * If the MediaPlayer failed to initialize properly, this call will fail gracefully and log the
+ * failed attempt. Media keys will not be routed.
+ */
+ private void getMediaKeyFocus() {
+ if (DBG) Log.d(TAG, "getMediaKeyFocus()");
+ if (mMediaPlayer == null) {
+ Log.w(TAG, "Media player is null. Can't get media key focus. Media keys may not route");
+ return;
+ }
+ mMediaPlayer.start();
+ }
/**
* getBluetoothMediaBrowserService()
@@ -311,6 +399,7 @@
if (DBG) Log.d(TAG, "onPrepare");
if (mA2dpSinkService != null) {
mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+ getMediaKeyFocus();
}
}
@@ -336,6 +425,7 @@
// Play the item if possible.
if (mA2dpSinkService != null) {
mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+ getMediaKeyFocus();
}
mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
}
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 6d0c430..ee7fb58 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -455,6 +455,7 @@
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
}
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index 17f094a..00fd7ba 100644
--- a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -38,6 +38,7 @@
import com.android.bluetooth.btservice.AdapterService;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -586,17 +587,15 @@
BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
synchronized (mMetadataCache) {
mMetadataCache.forEach((address, metadata) -> {
- for (BluetoothDevice device : bondedDevices) {
- if (!device.getAddress().equals(address)
- && !address.equals(LOCAL_STORAGE)) {
- // Report metadata change to null
- List<Integer> list = metadata.getChangedCustomizedMeta();
- for (int key : list) {
- mAdapterService.metadataChanged(address, key, null);
- }
- Log.i(TAG, "remove unpaired device from database " + address);
- deleteDatabase(mMetadataCache.get(address));
+ if (!address.equals(LOCAL_STORAGE)
+ && !Arrays.asList(bondedDevices).stream().anyMatch(device ->
+ address.equals(device.getAddress()))) {
+ List<Integer> list = metadata.getChangedCustomizedMeta();
+ for (int key : list) {
+ mAdapterService.metadataChanged(address, key, null);
}
+ Log.i(TAG, "remove unpaired device from database " + address);
+ deleteDatabase(mMetadataCache.get(address));
}
});
}
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index 96022df..d54a25a 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -89,6 +89,9 @@
/** Whether the calling app has location permission */
boolean hasLocationPermission;
+ /** Whether the calling app has bluetooth privileged permission */
+ boolean hasBluetoothPrivilegedPermission;
+
/** The user handle of the app that started the scan */
UserHandle mUserHandle;
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 1323bb1..8a3a1aa 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -300,6 +300,20 @@
== PERMISSION_GRANTED);
}
+ private boolean permissionCheck(ClientMap.App app, int connId, int handle) {
+ Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+ if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
+ return true;
+ }
+
+ if (!app.hasBluetoothPrivilegedPermission
+ && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)== PERMISSION_GRANTED) {
+ app.hasBluetoothPrivilegedPermission = true;
+ }
+
+ return app.hasBluetoothPrivilegedPermission;
+ }
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (GattDebugUtils.handleDebugAction(this, intent)) {
@@ -1390,13 +1404,12 @@
+ data.length);
}
- if (!permissionCheck(connId, handle)) {
- Log.w(TAG, "onNotify() - permission check failed!");
- return;
- }
-
ClientMap.App app = mClientMap.getByConnId(connId);
if (app != null) {
+ if (!permissionCheck(app, connId, handle)) {
+ Log.w(TAG, "onNotify() - permission check failed!");
+ return;
+ }
app.callback.onNotify(address, handle, data);
}
}
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 5c3fa4f..6d14e59 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -195,17 +195,17 @@
}
} else {
Log.w(TAG, "Socket CONNECT Failure ");
- mPbapClientStateMachine.obtainMessage(
- PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
+ mPbapClientStateMachine.sendMessage(
+ PbapClientStateMachine.MSG_CONNECTION_FAILED);
return;
}
if (connectObexSession()) {
- mPbapClientStateMachine.obtainMessage(
- PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
+ mPbapClientStateMachine.sendMessage(
+ PbapClientStateMachine.MSG_CONNECTION_COMPLETE);
} else {
- mPbapClientStateMachine.obtainMessage(
- PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
+ mPbapClientStateMachine.sendMessage(
+ PbapClientStateMachine.MSG_CONNECTION_FAILED);
}
break;
@@ -235,8 +235,7 @@
removeAccount(mAccount);
removeCallLog(mAccount);
- mPbapClientStateMachine.obtainMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED)
- .sendToTarget();
+ mPbapClientStateMachine.sendMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED);
break;
case MSG_DOWNLOAD:
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
new file mode 100644
index 0000000..4e6c351
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.a2dp;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class A2dpCodecConfigTest {
+ private Context mTargetContext;
+ private A2dpCodecConfig mA2dpCodecConfig;
+ private BluetoothAdapter mAdapter;
+ private BluetoothCodecConfig mCodecConfigSbc;
+ private BluetoothCodecConfig mCodecConfigAac;
+ private BluetoothCodecConfig mCodecConfigAptx;
+ private BluetoothCodecConfig mCodecConfigAptxHd;
+ private BluetoothCodecConfig mCodecConfigLdac;
+ private BluetoothDevice mTestDevice;
+
+ @Mock private A2dpNativeInterface mA2dpNativeInterface;
+
+ static final int SBC_PRIORITY_DEFAULT = 1001;
+ static final int AAC_PRIORITY_DEFAULT = 2001;
+ static final int APTX_PRIORITY_DEFAULT = 3001;
+ static final int APTX_HD_PRIORITY_DEFAULT = 4001;
+ static final int LDAC_PRIORITY_DEFAULT = 5001;
+ static final int PRIORITY_HIGH = 1000000;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mTargetContext = InstrumentationRegistry.getTargetContext();
+ // Set up mocks and test assets
+ MockitoAnnotations.initMocks(this);
+
+ mA2dpCodecConfig = new A2dpCodecConfig(mTargetContext, mA2dpNativeInterface);
+ mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+
+ doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
+ any(BluetoothDevice.class),
+ any(BluetoothCodecConfig[].class));
+
+ // Create sample codec configs
+ mCodecConfigSbc = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigAac = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ AAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigAptx = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+ APTX_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigAptxHd = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+ APTX_HD_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigLdac = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testAssignCodecConfigPriorities() {
+ BluetoothCodecConfig[] codecConfigs = mA2dpCodecConfig.codecConfigPriorities();
+ for (BluetoothCodecConfig config : codecConfigs) {
+ switch(config.getCodecType()) {
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+ Assert.assertEquals(config.getCodecPriority(), SBC_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+ Assert.assertEquals(config.getCodecPriority(), AAC_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+ Assert.assertEquals(config.getCodecPriority(), APTX_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+ Assert.assertEquals(config.getCodecPriority(), APTX_HD_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+ Assert.assertEquals(config.getCodecPriority(), LDAC_PRIORITY_DEFAULT);
+ break;
+ }
+ }
+ }
+
+ @Test
+ public void testSetCodecPreference_priorityHighToDefault() {
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, AAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, APTX_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, APTX_HD_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ false);
+ }
+
+ @Test
+ public void testSetCodecPreference_priorityDefaultToHigh() {
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ false);
+ }
+
+ @Test
+ public void testSetCodecPreference_priorityHighToHigh() {
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ false);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ }
+
+ @Test
+ public void testSetCodecPreference_parametersChange() {
+ int unSupportedParameter = 200;
+
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ false);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ true);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ unSupportedParameter,
+ false);
+
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ true);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ true);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ unSupportedParameter,
+ false);
+
+ testSetCodecPreference_parametersChangeCase(
+ unSupportedParameter,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ false);
+ testSetCodecPreference_parametersChangeCase(
+ unSupportedParameter,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ false);
+ testSetCodecPreference_parametersChangeCase(
+ unSupportedParameter,
+ unSupportedParameter,
+ false);
+ }
+
+ @Test
+ public void testDisableOptionalCodecs() {
+ int verifyCount = 0;
+ BluetoothCodecConfig[] codecConfigArray =
+ new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+ codecConfigArray[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
+ BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_NONE,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ // Test don't invoke to native when current codec is SBC
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigSbc);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+ // Test invoke to native when current codec is not SBC
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAac);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptx);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigLdac);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ }
+
+ @Test
+ public void testEnableOptionalCodecs() {
+ int verifyCount = 0;
+ BluetoothCodecConfig[] codecConfigArray =
+ new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+ codecConfigArray[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_NONE,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ // Test invoke to native when current codec is SBC
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigSbc);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+ // Test don't invoke to native when current codec is not SBC
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAac);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptx);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigLdac);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ }
+
+ private void testSetCodecPreference_parametersChangeCase(int sampleRate, int bitPerSample,
+ boolean invokeNative) {
+ BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[1];
+ invalidSelectableCodecs[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
+ (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+
+ BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[2];
+ selectableCodecs[0] = mCodecConfigSbc;
+ selectableCodecs[1] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
+ (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
+ codecConfigArray[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ sampleRate,
+ bitPerSample,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
+ invalidSelectableCodecs,
+ invalidSelectableCodecs);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+
+ codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
+ selectableCodecs,
+ selectableCodecs);
+
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+ verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+ }
+
+ private void testSetCodecPreference_codecPriorityChangeCase(int newCodecType,
+ int newCodecPriority, int oldCodecType, int oldCodecPriority, boolean invokeNative) {
+ BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[5];
+ selectableCodecs[0] = mCodecConfigSbc;
+ selectableCodecs[1] = mCodecConfigAac;
+ selectableCodecs[2] = mCodecConfigAptx;
+ selectableCodecs[3] = mCodecConfigAptxHd;
+ selectableCodecs[4] = mCodecConfigLdac;
+
+ BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[4];
+ invalidSelectableCodecs[0] = mCodecConfigAac;
+ invalidSelectableCodecs[1] = mCodecConfigAptx;
+ invalidSelectableCodecs[2] = mCodecConfigAptxHd;
+ invalidSelectableCodecs[3] = mCodecConfigLdac;
+
+ BluetoothCodecConfig oldCodecConfig = new BluetoothCodecConfig(
+ oldCodecType,
+ oldCodecPriority,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
+ codecConfigArray[0] = new BluetoothCodecConfig(
+ newCodecType,
+ newCodecPriority,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ invalidSelectableCodecs,
+ invalidSelectableCodecs);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+
+ codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ selectableCodecs,
+ selectableCodecs);
+
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+ verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index af872d1..9c01854 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -1073,7 +1073,6 @@
BluetoothCodecConfig.BITS_PER_SAMPLE_16,
BluetoothCodecConfig.CHANNEL_MODE_STEREO,
0, 0, 0, 0); // Codec-specific fields
- BluetoothCodecConfig codecConfig = codecConfigSbc;
BluetoothCodecConfig[] codecsLocalCapabilities;
BluetoothCodecConfig[] codecsSelectableCapabilities;
@@ -1090,24 +1089,36 @@
codecsLocalCapabilities[0] = codecConfigSbc;
codecsSelectableCapabilities[0] = codecConfigSbc;
}
- BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfig,
- codecsLocalCapabilities,
- codecsSelectableCapabilities);
+ BluetoothCodecConfig[] badCodecsSelectableCapabilities;
+ badCodecsSelectableCapabilities = new BluetoothCodecConfig[1];
+ badCodecsSelectableCapabilities[0] = codecConfigAac;
+
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfigSbc,
+ codecsLocalCapabilities, codecsSelectableCapabilities);
+ BluetoothCodecStatus badCodecStatus = new BluetoothCodecStatus(codecConfigAac,
+ codecsLocalCapabilities, badCodecsSelectableCapabilities);
when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice))
.thenReturn(previousSupport);
when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice))
.thenReturn(previousEnabled);
- connectDeviceWithCodecStatus(mTestDevice, codecStatus);
+ // Generate connection request from native with bad codec status
+ connectDeviceWithCodecStatus(mTestDevice, badCodecStatus);
+ generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+
+ // Generate connection request from native with good codec status
+ connectDeviceWithCodecStatus(mTestDevice, codecStatus);
+ generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+
+ // Check optional codec status is set properly
verify(mDatabaseManager, times(verifyNotSupportTime)).setA2dpSupportsOptionalCodecs(
mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
verify(mDatabaseManager, times(verifySupportTime)).setA2dpSupportsOptionalCodecs(
mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
verify(mDatabaseManager, times(verifyEnabledTime)).setA2dpOptionalCodecsEnabled(
mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
-
- generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTED);
}
}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
index 0b825bf..b8b69c0 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -20,6 +20,8 @@
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -54,6 +56,9 @@
private BluetoothDevice mTestDevice;
private static final int TIMEOUT_MS = 1000; // 1s
+ private BluetoothCodecConfig mCodecConfigSbc;
+ private BluetoothCodecConfig mCodecConfigAac;
+
@Mock private AdapterService mAdapterService;
@Mock private A2dpService mA2dpService;
@Mock private A2dpNativeInterface mA2dpNativeInterface;
@@ -72,6 +77,22 @@
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+ // Set up sample codec config
+ mCodecConfigSbc = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigAac = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
// Set up thread and looper
mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
mHandlerThread.start();
@@ -255,4 +276,87 @@
Assert.assertThat(mA2dpStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
}
+
+ /**
+ * Test that codec config change been reported to A2dpService properly.
+ */
+ @Test
+ public void testProcessCodecConfigEvent() {
+ testProcessCodecConfigEventCase(false);
+ }
+
+ /**
+ * Test that codec config change been reported to A2dpService properly when
+ * A2DP hardware offloading is enabled.
+ */
+ @Test
+ public void testProcessCodecConfigEvent_OffloadEnabled() {
+ testProcessCodecConfigEventCase(true);
+ }
+
+ /**
+ * Helper methold to test processCodecConfigEvent()
+ */
+ public void testProcessCodecConfigEventCase(boolean offloadEnabled) {
+ if (offloadEnabled) {
+ mA2dpStateMachine.mA2dpOffloadEnabled = true;
+ }
+
+ doNothing().when(mA2dpService).codecConfigUpdated(any(BluetoothDevice.class),
+ any(BluetoothCodecStatus.class), anyBoolean());
+ doNothing().when(mA2dpService).updateOptionalCodecsSupport(any(BluetoothDevice.class));
+ allowConnection(true);
+
+ BluetoothCodecConfig[] codecsSelectableSbc;
+ codecsSelectableSbc = new BluetoothCodecConfig[1];
+ codecsSelectableSbc[0] = mCodecConfigSbc;
+
+ BluetoothCodecConfig[] codecsSelectableSbcAac;
+ codecsSelectableSbcAac = new BluetoothCodecConfig[2];
+ codecsSelectableSbcAac[0] = mCodecConfigSbc;
+ codecsSelectableSbcAac[1] = mCodecConfigAac;
+
+ BluetoothCodecStatus codecStatusSbcAndSbc = new BluetoothCodecStatus(mCodecConfigSbc,
+ codecsSelectableSbcAac, codecsSelectableSbc);
+ BluetoothCodecStatus codecStatusSbcAndSbcAac = new BluetoothCodecStatus(mCodecConfigSbc,
+ codecsSelectableSbcAac, codecsSelectableSbcAac);
+ BluetoothCodecStatus codecStatusAacAndSbcAac = new BluetoothCodecStatus(mCodecConfigAac,
+ codecsSelectableSbcAac, codecsSelectableSbcAac);
+
+ // Set default codec status when device disconnected
+ // Selected codec = SBC, selectable codec = SBC
+ mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbc);
+ verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbc, false);
+
+ // Inject an event to change state machine to connected state
+ A2dpStackEvent connStCh =
+ new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ connStCh.device = mTestDevice;
+ connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+ mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh);
+
+ // Verify that the expected number of broadcasts are executed:
+ // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
+ // - one call to broadcastAudioState() when entering Connected state
+ ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+ verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(intentArgument2.capture(),
+ anyString());
+
+ // Verify that state machine update optional codec when enter connected state
+ verify(mA2dpService, times(1)).updateOptionalCodecsSupport(mTestDevice);
+
+ // Change codec status when device connected.
+ // Selected codec = SBC, selectable codec = SBC+AAC
+ mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbcAac);
+ if (!offloadEnabled) {
+ verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbcAac, true);
+ }
+ verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+
+ // Update selected codec with selectable codec unchanged.
+ // Selected codec = AAC, selectable codec = SBC+AAC
+ mA2dpStateMachine.processCodecConfigEvent(codecStatusAacAndSbcAac);
+ verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusAacAndSbcAac, false);
+ verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+ }
}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 21a1228..651971c 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -440,7 +440,8 @@
Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(batteryLevel,
intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15));
- Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, intent.getFlags());
+ Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, intent.getFlags());
}
private static Intent getHeadsetConnectionStateChangedIntent(BluetoothDevice device,
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
index 348204b..1b6cbc3 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -50,10 +50,12 @@
private MetadataDatabase mDatabase;
private DatabaseManager mDatabaseManager;
private BluetoothDevice mTestDevice;
+ private BluetoothDevice mTestDevice2;
private static final String LOCAL_STORAGE = "LocalStorage";
private static final String TEST_BT_ADDR = "11:22:33:44:55:66";
- private static final String OTHER_BT_ADDR = "11:11:11:11:11:11";
+ private static final String OTHER_BT_ADDR1 = "11:11:11:11:11:11";
+ private static final String OTHER_BT_ADDR2 = "22:22:22:22:22:22";
private static final int A2DP_SUPPORT_OP_CODEC_TEST = 0;
private static final int A2DP_ENALBED_OP_CODEC_TEST = 1;
private static final int MAX_META_ID = 16;
@@ -201,15 +203,13 @@
}
@Test
- public void testRemoveUnusedMetadata() {
- // Insert three devices to database and cache, only mTestDevice is
+ public void testRemoveUnusedMetadata_WithSingleBondedDevice() {
+ // Insert two devices to database and cache, only mTestDevice is
// in the bonded list
- BluetoothDevice otherDevice = BluetoothAdapter.getDefaultAdapter()
- .getRemoteDevice(OTHER_BT_ADDR);
- Metadata otherData = new Metadata(OTHER_BT_ADDR);
+ Metadata otherData = new Metadata(OTHER_BT_ADDR1);
// Add metadata for otherDevice
otherData.setCustomizedMeta(0, "value");
- mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR, otherData);
+ mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR1, otherData);
mDatabase.insert(otherData);
Metadata data = new Metadata(TEST_BT_ADDR);
@@ -221,7 +221,7 @@
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
// Check removed device report metadata changed to null
- verify(mAdapterService).metadataChanged(OTHER_BT_ADDR, 0, null);
+ verify(mAdapterService).metadataChanged(OTHER_BT_ADDR1, 0, null);
List<Metadata> list = mDatabase.load();
@@ -239,6 +239,61 @@
}
@Test
+ public void testRemoveUnusedMetadata_WithMultiBondedDevices() {
+ // Insert three devices to database and cache, otherDevice1 and otherDevice2
+ // are in the bonded list
+
+ // Add metadata for TEST_BT_ADDR
+ Metadata testData = new Metadata(TEST_BT_ADDR);
+ testData.setCustomizedMeta(0, "value");
+ mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, testData);
+ mDatabase.insert(testData);
+
+ // Add metadata for OTHER_BT_ADDR1
+ Metadata otherData1 = new Metadata(OTHER_BT_ADDR1);
+ otherData1.setCustomizedMeta(0, "value");
+ mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR1, otherData1);
+ mDatabase.insert(otherData1);
+
+ // Add metadata for OTHER_BT_ADDR2
+ Metadata otherData2 = new Metadata(OTHER_BT_ADDR2);
+ otherData2.setCustomizedMeta(0, "value");
+ mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR2, otherData2);
+ mDatabase.insert(otherData2);
+
+ // Add OTHER_BT_ADDR1 OTHER_BT_ADDR2 to bonded devices
+ BluetoothDevice otherDevice1 = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice(OTHER_BT_ADDR1);
+ BluetoothDevice otherDevice2 = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice(OTHER_BT_ADDR2);
+ BluetoothDevice[] bondedDevices = {otherDevice1, otherDevice2};
+ doReturn(bondedDevices).when(mAdapterService).getBondedDevices();
+
+ mDatabaseManager.removeUnusedMetadata();
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ // Check TEST_BT_ADDR report metadata changed to null
+ verify(mAdapterService).metadataChanged(TEST_BT_ADDR, 0, null);
+
+ // Check number of metadata in the database
+ List<Metadata> list = mDatabase.load();
+ // OTHER_BT_ADDR1 and OTHER_BT_ADDR2 should still in database
+ Assert.assertEquals(2, list.size());
+
+ // Check whether the devices are in the database
+ Metadata checkData1 = list.get(0);
+ Assert.assertEquals(OTHER_BT_ADDR1, checkData1.getAddress());
+ Metadata checkData2 = list.get(1);
+ Assert.assertEquals(OTHER_BT_ADDR2, checkData2.getAddress());
+
+ mDatabase.deleteAll();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ mDatabaseManager.mMetadataCache.clear();
+
+ }
+
+ @Test
public void testSetGetCustomMeta() {
int badKey = 100;
String value = "input value";