Merge "Fix bonded address been removed unexpected"
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/avrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index 99e9976..b5886c2 100644
--- a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -152,11 +152,7 @@
case MSG_TIMEOUT: {
Log.v(TAG, "Timed out waiting for players");
- for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
- if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
- wrapper.disconnect();
- }
- mPendingPlayers.clear();
+ removePendingPlayers();
} break;
}
@@ -169,4 +165,21 @@
}
};
}
+
+ private void removePendingPlayers() {
+ for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
+ if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
+ wrapper.disconnect();
+ }
+ mPendingPlayers.clear();
+ }
+
+ void cleanup() {
+ if (mPendingPlayers.size() != 0) {
+ Log.i(TAG, "Bluetooth turn off with " + mPendingPlayers.size() + " pending player(s)");
+ mHandler.removeMessages(MSG_TIMEOUT);
+ removePendingPlayers();
+ mHandler = null;
+ }
+ }
}
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 6148d4d..10d99bf 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -89,6 +89,7 @@
private int mActivePlayerId = NO_ACTIVE_PLAYER;
private AvrcpTargetService.ListCallback mCallback;
+ private BrowsablePlayerConnector mBrowsablePlayerConnector;
interface MediaUpdateCallback {
void run(MediaData data);
@@ -140,8 +141,8 @@
.getPackageManager()
.queryIntentServices(intent, PackageManager.MATCH_ALL);
- BrowsablePlayerConnector.connectToPlayers(mContext, mLooper, playerList,
- (List<BrowsedPlayerWrapper> players) -> {
+ mBrowsablePlayerConnector = BrowsablePlayerConnector.connectToPlayers(mContext, mLooper,
+ playerList, (List<BrowsedPlayerWrapper> players) -> {
Log.i(TAG, "init: Browsable Player list size is " + players.size());
// Check to see if the list has been cleaned up before this completed
@@ -196,6 +197,9 @@
}
mMediaPlayers.clear();
+ if (mBrowsablePlayerConnector != null) {
+ mBrowsablePlayerConnector.cleanup();
+ }
for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
player.disconnect();
}
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/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 40528b2..76cf66d 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -66,8 +66,6 @@
import android.util.SparseArray;
import android.util.StatsLog;
-import androidx.room.Room;
-
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
@@ -426,9 +424,7 @@
mActiveDeviceManager.start();
mDatabaseManager = new DatabaseManager(this);
- MetadataDatabase database = Room.databaseBuilder(this,
- MetadataDatabase.class, MetadataDatabase.DATABASE_NAME).build();
- mDatabaseManager.start(database);
+ mDatabaseManager.start(MetadataDatabase.createDatabase(this));
mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(),
Looper.getMainLooper());
@@ -2712,7 +2708,7 @@
*/
@VisibleForTesting
public void metadataChanged(String address, int key, String value) {
- BluetoothDevice device = mRemoteDevices.getDevice(address.getBytes());
+ BluetoothDevice device = mRemoteDevices.getDevice(Utils.getBytesFromAddress(address));
if (mMetadataListeners.containsKey(device)) {
ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device);
for (IBluetoothMetadataListener listener : list) {
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 3ba439d..00fd7ba 100644
--- a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -268,8 +268,9 @@
* {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
* {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
* {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
- * {@link BluetoothProfile#MAP}, {@link BluetoothProfile#MAP_CLIENT},
- * {@link BluetoothProfile#SAP}, {@link BluetoothProfile#HEARING_AID}
+ * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
+ * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
+ * {@link BluetoothProfile#HEARING_AID}
* @param newPriority the priority to set; one of
* {@link BluetoothProfile#PRIORITY_UNDEFINED},
* {@link BluetoothProfile#PRIORITY_OFF},
@@ -326,8 +327,9 @@
* {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
* {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
* {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
- * {@link BluetoothProfile#MAP}, {@link BluetoothProfile#MAP_CLIENT},
- * {@link BluetoothProfile#SAP}, {@link BluetoothProfile#HEARING_AID}
+ * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
+ * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
+ * {@link BluetoothProfile#HEARING_AID}
* @return the profile priority of the device; one of
* {@link BluetoothProfile#PRIORITY_UNDEFINED},
* {@link BluetoothProfile#PRIORITY_OFF},
@@ -671,6 +673,9 @@
int pbapPriority = Settings.Global.getInt(contentResolver,
Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
BluetoothProfile.PRIORITY_UNDEFINED);
+ int pbapClientPriority = Settings.Global.getInt(contentResolver,
+ Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
+ BluetoothProfile.PRIORITY_UNDEFINED);
int sapPriority = Settings.Global.getInt(contentResolver,
Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
BluetoothProfile.PRIORITY_UNDEFINED);
@@ -690,6 +695,7 @@
data.setProfilePriority(BluetoothProfile.HID_HOST, hidHostPriority);
data.setProfilePriority(BluetoothProfile.PAN, panPriority);
data.setProfilePriority(BluetoothProfile.PBAP, pbapPriority);
+ data.setProfilePriority(BluetoothProfile.PBAP_CLIENT, pbapClientPriority);
data.setProfilePriority(BluetoothProfile.MAP, mapPriority);
data.setProfilePriority(BluetoothProfile.MAP_CLIENT, mapClientPriority);
data.setProfilePriority(BluetoothProfile.SAP, sapPriority);
diff --git a/src/com/android/bluetooth/btservice/storage/Metadata.java b/src/com/android/bluetooth/btservice/storage/Metadata.java
index 8678814..5700e69 100644
--- a/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -37,7 +37,7 @@
public boolean migrated;
@Embedded
- public ProfilePrioritiesEntity profilePriorites;
+ public ProfilePrioritiesEntity profilePriorities;
@Embedded
@NonNull
@@ -49,7 +49,7 @@
Metadata(String address) {
this.address = address;
migrated = false;
- profilePriorites = new ProfilePrioritiesEntity();
+ profilePriorities = new ProfilePrioritiesEntity();
publicMetadata = new CustomizedMetadataEntity();
a2dpSupportsOptionalCodecs = BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
@@ -62,37 +62,40 @@
void setProfilePriority(int profile, int priority) {
switch (profile) {
case BluetoothProfile.A2DP:
- profilePriorites.a2dp_priority = priority;
+ profilePriorities.a2dp_priority = priority;
break;
case BluetoothProfile.A2DP_SINK:
- profilePriorites.a2dp_sink_priority = priority;
+ profilePriorities.a2dp_sink_priority = priority;
break;
case BluetoothProfile.HEADSET:
- profilePriorites.hfp_priority = priority;
+ profilePriorities.hfp_priority = priority;
break;
case BluetoothProfile.HEADSET_CLIENT:
- profilePriorites.hfp_client_priority = priority;
+ profilePriorities.hfp_client_priority = priority;
break;
case BluetoothProfile.HID_HOST:
- profilePriorites.hid_host_priority = priority;
+ profilePriorities.hid_host_priority = priority;
break;
case BluetoothProfile.PAN:
- profilePriorites.pan_priority = priority;
+ profilePriorities.pan_priority = priority;
break;
case BluetoothProfile.PBAP:
- profilePriorites.pbap_priority = priority;
+ profilePriorities.pbap_priority = priority;
+ break;
+ case BluetoothProfile.PBAP_CLIENT:
+ profilePriorities.pbap_client_priority = priority;
break;
case BluetoothProfile.MAP:
- profilePriorites.map_priority = priority;
+ profilePriorities.map_priority = priority;
break;
case BluetoothProfile.MAP_CLIENT:
- profilePriorites.map_client_priority = priority;
+ profilePriorities.map_client_priority = priority;
break;
case BluetoothProfile.SAP:
- profilePriorites.sap_priority = priority;
+ profilePriorities.sap_priority = priority;
break;
case BluetoothProfile.HEARING_AID:
- profilePriorites.hearing_aid_priority = priority;
+ profilePriorities.hearing_aid_priority = priority;
break;
default:
throw new IllegalArgumentException("invalid profile " + profile);
@@ -102,27 +105,29 @@
int getProfilePriority(int profile) {
switch (profile) {
case BluetoothProfile.A2DP:
- return profilePriorites.a2dp_priority;
+ return profilePriorities.a2dp_priority;
case BluetoothProfile.A2DP_SINK:
- return profilePriorites.a2dp_sink_priority;
+ return profilePriorities.a2dp_sink_priority;
case BluetoothProfile.HEADSET:
- return profilePriorites.hfp_priority;
+ return profilePriorities.hfp_priority;
case BluetoothProfile.HEADSET_CLIENT:
- return profilePriorites.hfp_client_priority;
+ return profilePriorities.hfp_client_priority;
case BluetoothProfile.HID_HOST:
- return profilePriorites.hid_host_priority;
+ return profilePriorities.hid_host_priority;
case BluetoothProfile.PAN:
- return profilePriorites.pan_priority;
+ return profilePriorities.pan_priority;
case BluetoothProfile.PBAP:
- return profilePriorites.pbap_priority;
+ return profilePriorities.pbap_priority;
+ case BluetoothProfile.PBAP_CLIENT:
+ return profilePriorities.pbap_client_priority;
case BluetoothProfile.MAP:
- return profilePriorites.map_priority;
+ return profilePriorities.map_priority;
case BluetoothProfile.MAP_CLIENT:
- return profilePriorites.map_client_priority;
+ return profilePriorities.map_client_priority;
case BluetoothProfile.SAP:
- return profilePriorites.sap_priority;
+ return profilePriorities.sap_priority;
case BluetoothProfile.HEARING_AID:
- return profilePriorites.hearing_aid_priority;
+ return profilePriorities.hearing_aid_priority;
}
return BluetoothProfile.PRIORITY_UNDEFINED;
}
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index a43d566..118dc22 100644
--- a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -16,15 +16,20 @@
package com.android.bluetooth.btservice.storage;
+import android.content.Context;
+
import androidx.room.Database;
+import androidx.room.Room;
import androidx.room.RoomDatabase;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
import java.util.List;
/**
* MetadataDatabase is a Room database stores Bluetooth persistence data
*/
-@Database(entities = {Metadata.class}, exportSchema = false, version = 100)
+@Database(entities = {Metadata.class}, exportSchema = false, version = 101)
public abstract class MetadataDatabase extends RoomDatabase {
/**
* The database file name
@@ -34,6 +39,19 @@
protected abstract MetadataDao mMetadataDao();
/**
+ * Create a {@link MetadataDatabase} database
+ *
+ * @param context the Context to create database
+ * @return the created {@link MetadataDatabase}
+ */
+ public static MetadataDatabase createDatabase(Context context) {
+ return Room.databaseBuilder(context,
+ MetadataDatabase.class, DATABASE_NAME)
+ .addMigrations(MIGRATION_100_101)
+ .build();
+ }
+
+ /**
* Insert a {@link Metadata} to database
*
* @param metadata the data wish to put into storage
@@ -66,4 +84,11 @@
public void deleteAll() {
mMetadataDao().deleteAll();
}
+
+ private static final Migration MIGRATION_100_101 = new Migration(100, 101) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `pbap_client_priority` INTEGER");
+ }
+ };
}
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
index 5537b93..f4ea719 100644
--- a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -30,6 +30,7 @@
public int hid_host_priority;
public int pan_priority;
public int pbap_priority;
+ public int pbap_client_priority;
public int map_priority;
public int sap_priority;
public int hearing_aid_priority;
@@ -43,6 +44,7 @@
hid_host_priority = BluetoothProfile.PRIORITY_UNDEFINED;
pan_priority = BluetoothProfile.PRIORITY_UNDEFINED;
pbap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+ pbap_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
map_priority = BluetoothProfile.PRIORITY_UNDEFINED;
sap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
hearing_aid_priority = BluetoothProfile.PRIORITY_UNDEFINED;
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 f3346bf..8a3a1aa 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -63,6 +63,7 @@
import com.android.bluetooth.util.NumberUtils;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -98,6 +99,9 @@
private static final int ET_LEGACY_MASK = 0x10;
+ private static final UUID HID_SERVICE_UUID =
+ UUID.fromString("00001812-0000-1000-8000-00805F9B34FB");
+
private static final UUID[] HID_UUIDS = {
UUID.fromString("00002A4A-0000-1000-8000-00805F9B34FB"),
UUID.fromString("00002A4B-0000-1000-8000-00805F9B34FB"),
@@ -108,9 +112,8 @@
private static final UUID ANDROID_TV_REMOTE_SERVICE_UUID =
UUID.fromString("AB5E0001-5A21-4F05-BC7D-AF01F617B664");
- private static final UUID[] FIDO_UUIDS = {
- UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB") // U2F
- };
+ private static final UUID FIDO_SERVICE_UUID =
+ UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB"); // U2F
/**
* Keep the arguments passed in for the PendingIntent.
@@ -160,13 +163,17 @@
private int mMaxScanFilters;
private static final int NUM_SCAN_EVENTS_KEPT = 20;
+
/**
* Internal list of scan events to use with the proto
*/
- private final ArrayList<BluetoothMetricsProto.ScanEvent> mScanEvents =
- new ArrayList<>(NUM_SCAN_EVENTS_KEPT);
+ private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents =
+ new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT);
- private final Map<Integer, List<BluetoothGattService>> mGattClientDatabases = new HashMap<>();
+ /**
+ * Set of restricted (which require a BLUETOOTH_PRIVILEGED permission) handles per connectionId.
+ */
+ private final Map<Integer, Set<Integer>> mRestrictedHandles = new HashMap<>();
private BluetoothAdapter mAdapter;
private AdvertiseManager mAdvertiseManager;
@@ -277,36 +284,34 @@
sGattService = instance;
}
- boolean permissionCheck(UUID uuid) {
- return !(isRestrictedCharUuid(uuid) && (0 != checkCallingOrSelfPermission(
- BLUETOOTH_PRIVILEGED)));
+ private boolean permissionCheck(UUID characteristicUuid) {
+ return !isHidCharUuid(characteristicUuid)
+ || (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+ == PERMISSION_GRANTED);
}
- boolean permissionCheck(int connId, int handle) {
- List<BluetoothGattService> db = mGattClientDatabases.get(connId);
- if (db == null) {
+ private boolean permissionCheck(int connId, int handle) {
+ Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+ if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
return true;
}
- for (BluetoothGattService service : db) {
- for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
- if (handle == characteristic.getInstanceId()) {
- return !((isRestrictedCharUuid(characteristic.getUuid())
- || isRestrictedSrvcUuid(service.getUuid()))
- && (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
- }
+ return (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+ == PERMISSION_GRANTED);
+ }
- for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
- if (handle == descriptor.getInstanceId()) {
- return !((isRestrictedCharUuid(characteristic.getUuid())
- || isRestrictedSrvcUuid(service.getUuid())) && (0
- != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
- }
- }
- }
+ private boolean permissionCheck(ClientMap.App app, int connId, int handle) {
+ Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+ if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
+ return true;
}
- return true;
+ if (!app.hasBluetoothPrivilegedPermission
+ && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)== PERMISSION_GRANTED) {
+ app.hasBluetoothPrivilegedPermission = true;
+ }
+
+ return app.hasBluetoothPrivilegedPermission;
}
@Override
@@ -1309,9 +1314,13 @@
}
List<BluetoothGattService> dbOut = new ArrayList<BluetoothGattService>();
+ Set<Integer> restrictedIds = new HashSet<>();
BluetoothGattService currSrvc = null;
BluetoothGattCharacteristic currChar = null;
+ boolean isRestrictedSrvc = false;
+ boolean isHidSrvc = false;
+ boolean isRestrictedChar = false;
for (GattDbElement el : db) {
switch (el.type) {
@@ -1323,6 +1332,12 @@
currSrvc = new BluetoothGattService(el.uuid, el.id, el.type);
dbOut.add(currSrvc);
+ isRestrictedSrvc =
+ isFidoSrvcUuid(el.uuid) || isAndroidTvRemoteSrvcUuid(el.uuid);
+ isHidSrvc = isHidSrvcUuid(el.uuid);
+ if (isRestrictedSrvc) {
+ restrictedIds.add(el.id);
+ }
break;
case GattDbElement.TYPE_CHARACTERISTIC:
@@ -1332,6 +1347,10 @@
currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0);
currSrvc.addCharacteristic(currChar);
+ isRestrictedChar = isRestrictedSrvc || (isHidSrvc && isHidCharUuid(el.uuid));
+ if (isRestrictedChar) {
+ restrictedIds.add(el.id);
+ }
break;
case GattDbElement.TYPE_DESCRIPTOR:
@@ -1340,6 +1359,9 @@
}
currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
+ if (isRestrictedChar) {
+ restrictedIds.add(el.id);
+ }
break;
case GattDbElement.TYPE_INCLUDED_SERVICE:
@@ -1358,8 +1380,10 @@
}
}
+ if (!restrictedIds.isEmpty()) {
+ mRestrictedHandles.put(connId, restrictedIds);
+ }
// Search is complete when there was error, or nothing more to process
- mGattClientDatabases.put(connId, dbOut);
app.callback.onSearchComplete(address, dbOut, 0 /* status */);
}
@@ -1380,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);
}
}
@@ -2995,15 +3018,11 @@
* Private functions
*************************************************************************/
- private boolean isRestrictedCharUuid(final UUID charUuid) {
- return isHidUuid(charUuid);
+ private boolean isHidSrvcUuid(final UUID uuid) {
+ return HID_SERVICE_UUID.equals(uuid);
}
- private boolean isRestrictedSrvcUuid(final UUID srvcUuid) {
- return isFidoUUID(srvcUuid) || isAndroidTvRemoteSrvcUUID(srvcUuid);
- }
-
- private boolean isHidUuid(final UUID uuid) {
+ private boolean isHidCharUuid(final UUID uuid) {
for (UUID hidUuid : HID_UUIDS) {
if (hidUuid.equals(uuid)) {
return true;
@@ -3012,17 +3031,12 @@
return false;
}
- private boolean isAndroidTvRemoteSrvcUUID(final UUID uuid) {
+ private boolean isAndroidTvRemoteSrvcUuid(final UUID uuid) {
return ANDROID_TV_REMOTE_SERVICE_UUID.equals(uuid);
}
- private boolean isFidoUUID(final UUID uuid) {
- for (UUID fidoUuid : FIDO_UUIDS) {
- if (fidoUuid.equals(uuid)) {
- return true;
- }
- }
- return false;
+ private boolean isFidoSrvcUuid(final UUID uuid) {
+ return FIDO_SERVICE_UUID.equals(uuid);
}
private int getDeviceType(BluetoothDevice device) {
@@ -3174,7 +3188,7 @@
void addScanEvent(BluetoothMetricsProto.ScanEvent event) {
synchronized (mScanEvents) {
if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) {
- mScanEvents.remove(0);
+ mScanEvents.remove();
}
mScanEvents.add(event);
}
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 39ca3ce..541bcf4 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -658,6 +658,10 @@
== HeadsetClientHalConstants.PEER_FEAT_3WAY) {
b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
}
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+ == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+ }
if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
== HeadsetClientHalConstants.PEER_FEAT_REJECT) {
b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
@@ -1782,6 +1786,10 @@
== HeadsetClientHalConstants.PEER_FEAT_3WAY) {
intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
}
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+ == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+ }
if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
== HeadsetClientHalConstants.PEER_FEAT_REJECT) {
intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
diff --git a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
index 91652a6..8ab9a1a 100644
--- a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
@@ -90,7 +90,8 @@
values.put(CallLog.Calls.TYPE, type);
values.put(Calls.PHONE_ACCOUNT_ID, mAccount.name);
List<PhoneData> phones = vcard.getPhoneList();
- if (phones == null || phones.get(0).getNumber().equals(";")) {
+ if (phones == null || phones.get(0).getNumber().equals(";")
+ || phones.get(0).getNumber().length() == 0) {
values.put(CallLog.Calls.NUMBER, "");
} else {
String phoneNumber = phones.get(0).getNumber();
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,