Add support for AVRCP 1.3.
* Add metadata support.
* Add player settings support.
* Add playback support.
A2DP Settings App support.
Bluetooth: A2DP Sink support for Settings App
- add support for A2DP Sink in Settings App. This will enable connection
initiation and updation on Settings App
- add framework Apis to support A2DP Sink. Any third party Apps can access
A2DP Sink priority of device and playing state of device
- add support for key to set priority. This manages priority of device for
A2DP Sink profile
Change-Id: If5f9139f37cdb9d200387877c7801075205c78a0
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
old mode 100644
new mode 100755
index 2e27345..74302f2
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -371,6 +371,89 @@
}
/**
+ * Set priority of the profile
+ *
+ * <p> The device should already be paired.
+ * Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
+ * {@link #PRIORITY_OFF},
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @param device Paired bluetooth device
+ * @param priority
+ * @return true if priority is set, false on error
+ * @hide
+ */
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ if (DBG) log("setPriority(" + device + ", " + priority + ")");
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ if (priority != BluetoothProfile.PRIORITY_OFF &&
+ priority != BluetoothProfile.PRIORITY_ON){
+ return false;
+ }
+ try {
+ return mService.setPriority(device, priority);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Get the priority of the profile.
+ *
+ * <p> The priority can be any of:
+ * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+ * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Bluetooth device
+ * @return priority of the device
+ * @hide
+ */
+ public int getPriority(BluetoothDevice device) {
+ if (VDBG) log("getPriority(" + device + ")");
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ try {
+ return mService.getPriority(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return BluetoothProfile.PRIORITY_OFF;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return BluetoothProfile.PRIORITY_OFF;
+ }
+
+ /**
+ * Check if A2DP profile is streaming music.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device BluetoothDevice device
+ */
+ public boolean isA2dpPlaying(BluetoothDevice device) {
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ try {
+ return mService.isA2dpPlaying(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
* Helper for converting a state to a string.
*
* For debug use only - strings are not internationalized.
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index b53a8fc..444e429 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -20,6 +20,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -28,8 +30,8 @@
import java.util.List;
/**
- * This class provides the public APIs to control the Bluetooth AVRCP Controller
- * profile.
+ * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
+ * supports player information, playback support and track metadata.
*
*<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
@@ -39,7 +41,7 @@
*/
public final class BluetoothAvrcpController implements BluetoothProfile {
private static final String TAG = "BluetoothAvrcpController";
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static final boolean VDBG = false;
/**
@@ -61,7 +63,63 @@
* receive.
*/
public static final String ACTION_CONNECTION_STATE_CHANGED =
- "android.bluetooth.acrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
+ "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
+
+ /**
+ * Intent used to broadcast the change in metadata state of playing track on the AVRCP
+ * AG.
+ *
+ * <p>This intent will have the two extras:
+ * <ul>
+ * <li> {@link #EXTRA_METADATA} - {@link MediaMetadata} containing the current metadata.</li>
+ * <li> {@link #EXTRA_PLAYBACK} - {@link PlaybackState} containing the current playback
+ * state. </li>
+ * </ul>
+ */
+ public static final String ACTION_TRACK_EVENT =
+ "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
+
+
+ /**
+ * Intent used to broadcast the change in player application setting state on AVRCP AG.
+ *
+ * <p>This intent will have the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
+ * most recent player setting. </li>
+ * </ul>
+ */
+ public static final String ACTION_PLAYER_SETTING =
+ "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
+
+ public static final String EXTRA_METADATA =
+ "android.bluetooth.avrcp-controller.profile.extra.METADATA";
+
+ public static final String EXTRA_PLAYBACK =
+ "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
+
+ public static final String EXTRA_PLAYER_SETTING =
+ "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
+
+ /*
+ * KeyCoded for Pass Through Commands
+ */
+ public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
+ public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
+ public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
+ public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
+ public static final int PASS_THRU_CMD_ID_STOP = 0x45;
+ public static final int PASS_THRU_CMD_ID_FF = 0x49;
+ public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
+ public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
+ public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
+ /* Key State Variables */
+ public static final int KEY_STATE_PRESSED = 0;
+ public static final int KEY_STATE_RELEASED = 1;
+ /* Group Navigation Key Codes */
+ public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
+ public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
+
private Context mContext;
private ServiceListener mServiceListener;
@@ -69,33 +127,33 @@
private BluetoothAdapter mAdapter;
final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
- new IBluetoothStateChangeCallback.Stub() {
- public void onBluetoothStateChange(boolean up) {
- if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
- if (!up) {
- if (VDBG) Log.d(TAG,"Unbinding service...");
- synchronized (mConnection) {
- try {
- mService = null;
- mContext.unbindService(mConnection);
- } catch (Exception re) {
- Log.e(TAG,"",re);
- }
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (VDBG) Log.d(TAG,"Unbinding service...");
+ synchronized (mConnection) {
+ try {
+ mService = null;
+ mContext.unbindService(mConnection);
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
}
- } else {
- synchronized (mConnection) {
- try {
- if (mService == null) {
- if (VDBG) Log.d(TAG,"Binding service...");
- doBind();
- }
- } catch (Exception re) {
- Log.e(TAG,"",re);
+ }
+ } else {
+ synchronized (mConnection) {
+ try {
+ if (mService == null) {
+ if (VDBG) Log.d(TAG,"Binding service...");
+ doBind();
}
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
}
}
}
- };
+ }
+ };
/**
* Create a BluetoothAvrcpController proxy object for interacting with the local
@@ -223,6 +281,104 @@
if (mService == null) Log.w(TAG, "Proxy not attached to service");
}
+ /**
+ * Gets the player application settings.
+ *
+ * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
+ */
+ public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "getPlayerSettings");
+ BluetoothAvrcpPlayerSettings settings = null;
+ if (mService != null && isEnabled()) {
+ try {
+ settings = mService.getPlayerSettings(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
+ return null;
+ }
+ }
+ return settings;
+ }
+
+ /**
+ * Gets the metadata for the current track.
+ *
+ * This should be usually called when application UI needs to be updated, eg. when the track
+ * changes or immediately after connecting and getting the current state.
+ * @return the {@link MediaMetadata} or {@link null} if there is an error.
+ */
+ public MediaMetadata getMetadata(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "getMetadata");
+ MediaMetadata metadata = null;
+ if (mService != null && isEnabled()) {
+ try {
+ metadata = mService.getMetadata(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
+ return null;
+ }
+ }
+ return metadata;
+ }
+
+ /**
+ * Gets the playback state for current track.
+ *
+ * When the application is first connecting it can use current track state to get playback info.
+ * For all further updates it should listen to notifications.
+ * @return the {@link PlaybackState} or {@link null} if there is an error.
+ */
+ public PlaybackState getPlaybackState(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "getPlaybackState");
+ PlaybackState playbackState = null;
+ if (mService != null && isEnabled()) {
+ try {
+ playbackState = mService.getPlaybackState(device);
+ } catch (RemoteException e) {
+ Log.e(TAG,
+ "Error talking to BT service in getPlaybackState() " + e);
+ return null;
+ }
+ }
+ return playbackState;
+ }
+
+ /**
+ * Sets the player app setting for current player.
+ * returns true in case setting is supported by remote, false otherwise
+ */
+ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+ if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.setPlayerApplicationSetting(plAppSetting);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /*
+ * Send Group Navigation Command to Remote.
+ * possible keycode values: next_grp, previous_grp defined above
+ */
+ public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState);
+ if (mService != null && isEnabled()) {
+ try {
+ mService.sendGroupNavigationCmd(device, keyCode, keyState);
+ return;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
+ return;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ }
+
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
diff --git a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.aidl b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.aidl
new file mode 100644
index 0000000..590fd63
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 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 android.bluetooth;
+
+parcelable BluetoothAvrcpPlayerSettings;
diff --git a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java
new file mode 100644
index 0000000..927cb56
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 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 android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class used to identify settings associated with the player on AG.
+ *
+ * {@hide}
+ */
+public final class BluetoothAvrcpPlayerSettings implements Parcelable {
+ public static final String TAG = "BluetoothAvrcpPlayerSettings";
+
+ /**
+ * Equalizer setting.
+ */
+ public static final int SETTING_EQUALIZER = 0x01;
+
+ /**
+ * Repeat setting.
+ */
+ public static final int SETTING_REPEAT = 0x02;
+
+ /**
+ * Shuffle setting.
+ */
+ public static final int SETTING_SHUFFLE = 0x04;
+
+ /**
+ * Scan mode setting.
+ */
+ public static final int SETTING_SCAN = 0x08;
+
+ /**
+ * Invalid state.
+ *
+ * Used for returning error codes.
+ */
+ public static final int STATE_INVALID = -1;
+
+ /**
+ * OFF state.
+ *
+ * Denotes a general OFF state. Applies to all settings.
+ */
+ public static final int STATE_OFF = 0x00;
+
+ /**
+ * ON state.
+ *
+ * Applies to {@link SETTING_EQUALIZER}.
+ */
+ public static final int STATE_ON = 0x01;
+
+ /**
+ * Single track repeat.
+ *
+ * Applies only to {@link SETTING_REPEAT}.
+ */
+ public static final int STATE_SINGLE_TRACK = 0x02;
+
+ /**
+ * All track repeat/shuffle.
+ *
+ * Applies to {@link SETTING_REPEAT}, {@link SETTING_SHUFFLE} and {@link SETTING_SCAN}.
+ */
+ public static final int STATE_ALL_TRACK = 0x03;
+
+ /**
+ * Group repeat/shuffle.
+ *
+ * Applies to {@link SETTING_REPEAT}, {@link SETTING_SHUFFLE} and {@link SETTING_SCAN}.
+ */
+ public static final int STATE_GROUP = 0x04;
+
+ /**
+ * List of supported settings ORed.
+ */
+ private int mSettings;
+
+ /**
+ * Hash map of current capability values.
+ */
+ private Map<Integer, Integer> mSettingsValue = new HashMap<Integer, Integer>();
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mSettings);
+ out.writeInt(mSettingsValue.size());
+ for (int k : mSettingsValue.keySet()) {
+ out.writeInt(k);
+ out.writeInt(mSettingsValue.get(k));
+ }
+ }
+
+ public static final Parcelable.Creator<BluetoothAvrcpPlayerSettings> CREATOR
+ = new Parcelable.Creator<BluetoothAvrcpPlayerSettings>() {
+ public BluetoothAvrcpPlayerSettings createFromParcel(Parcel in) {
+ return new BluetoothAvrcpPlayerSettings(in);
+ }
+
+ public BluetoothAvrcpPlayerSettings[] newArray(int size) {
+ return new BluetoothAvrcpPlayerSettings[size];
+ }
+ };
+
+ private BluetoothAvrcpPlayerSettings(Parcel in) {
+ mSettings = in.readInt();
+ int numSettings = in.readInt();
+ for (int i = 0; i < numSettings; i++) {
+ mSettingsValue.put(in.readInt(), in.readInt());
+ }
+ }
+
+ /**
+ * Create a new player settings object.
+ *
+ * @param settings a ORed value of SETTINGS_* defined above.
+ */
+ public BluetoothAvrcpPlayerSettings(int settings) {
+ mSettings = settings;
+ }
+
+ /**
+ * Get the supported settings.
+ *
+ * @return int ORed value of supported settings.
+ */
+ public int getSettings() {
+ return mSettings;
+ }
+
+ /**
+ * Add a setting value.
+ *
+ * The setting must be part of possible settings in {@link getSettings()}.
+ * @param setting setting config.
+ * @param value value for the setting.
+ * @throws IllegalStateException if the setting is not supported.
+ */
+ public void addSettingValue(int setting, int value) {
+ if ((setting & mSettings) == 0) {
+ Log.e(TAG, "Setting not supported: " + setting + " " + mSettings);
+ throw new IllegalStateException("Setting not supported: " + setting);
+ }
+ mSettingsValue.put(setting, value);
+ }
+
+ /**
+ * Get a setting value.
+ *
+ * The setting must be part of possible settings in {@link getSettings()}.
+ * @param setting setting config.
+ * @return value value for the setting.
+ * @throws IllegalStateException if the setting is not supported.
+ */
+ public int getSettingValue(int setting) {
+ if ((setting & mSettings) == 0) {
+ Log.e(TAG, "Setting not supported: " + setting + " " + mSettings);
+ throw new IllegalStateException("Setting not supported: " + setting);
+ }
+ Integer i = mSettingsValue.get(setting);
+ if (i == null) return -1;
+ return i;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
old mode 100644
new mode 100755
index 54bf4af..4a38287
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -283,6 +283,8 @@
public static final int PROFILE_PANU = 4;
/** @hide */
public static final int PROFILE_NAP = 5;
+ /** @hide */
+ public static final int PROFILE_A2DP_SINK = 6;
/**
* Check class bits for possible bluetooth profile support.
@@ -310,6 +312,21 @@
default:
return false;
}
+ } else if (profile == PROFILE_A2DP_SINK) {
+ if (hasService(Service.CAPTURE)) {
+ return true;
+ }
+ // By the A2DP spec, srcs must indicate the CAPTURE service.
+ // However if some device that do not, we try to
+ // match on some other class bits.
+ switch (getDeviceClass()) {
+ case Device.AUDIO_VIDEO_HIFI_AUDIO:
+ case Device.AUDIO_VIDEO_SET_TOP_BOX:
+ case Device.AUDIO_VIDEO_VCR :
+ return true;
+ default:
+ return false;
+ }
} else if (profile == PROFILE_HEADSET) {
// The render service class is required by the spec for HFP, so is a
// pretty good signal
diff --git a/core/java/android/bluetooth/IBluetoothA2dpSink.aidl b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
old mode 100644
new mode 100755
index b7c6476..d1458246
--- a/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
@@ -31,4 +31,7 @@
List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
int getConnectionState(in BluetoothDevice device);
BluetoothAudioConfig getAudioConfig(in BluetoothDevice device);
+ boolean setPriority(in BluetoothDevice device, int priority);
+ int getPriority(in BluetoothDevice device);
+ boolean isA2dpPlaying(in BluetoothDevice device);
}
diff --git a/core/java/android/bluetooth/IBluetoothAvrcpController.aidl b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
index f917a50..f1288d0 100644
--- a/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
+++ b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
@@ -16,7 +16,10 @@
package android.bluetooth;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
import android.bluetooth.BluetoothDevice;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
/**
* APIs for Bluetooth AVRCP controller service
@@ -28,4 +31,9 @@
List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
int getConnectionState(in BluetoothDevice device);
void sendPassThroughCmd(in BluetoothDevice device, int keyCode, int keyState);
+ BluetoothAvrcpPlayerSettings getPlayerSettings(in BluetoothDevice device);
+ MediaMetadata getMetadata(in BluetoothDevice device);
+ PlaybackState getPlaybackState(in BluetoothDevice device);
+ boolean setPlayerApplicationSetting(in BluetoothAvrcpPlayerSettings plAppSetting);
+ void sendGroupNavigationCmd(in BluetoothDevice device, int keyCode, int keyState);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
old mode 100644
new mode 100755
index d23a92f..3042aa9
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7416,6 +7416,9 @@
BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_";
/** {@hide} */
public static final String
+ BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX = "bluetooth_a2dp_src_priority_";
+ /** {@hide} */
+ public static final String
BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_";
/** {@hide} */
public static final String
@@ -7518,6 +7521,14 @@
}
/**
+ * Get the key that retrieves a bluetooth a2dp src's priority.
+ * @hide
+ */
+ public static final String getBluetoothA2dpSrcPriorityKey(String address) {
+ return BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
* Get the key that retrieves a bluetooth Input Device's priority.
* @hide
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
new file mode 100755
index 0000000..77f2e19
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2015 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+final class A2dpSinkProfile implements LocalBluetoothProfile {
+ private static final String TAG = "A2dpSinkProfile";
+ private static boolean V = true;
+
+ private BluetoothA2dpSink mService;
+ private boolean mIsProfileReady;
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+
+ static final ParcelUuid[] SRC_UUIDS = {
+ BluetoothUuid.AudioSource,
+ BluetoothUuid.AdvAudioDist,
+ };
+
+ static final String NAME = "A2DPSink";
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 5;
+
+ // These callbacks run on the main thread.
+ private final class A2dpSinkServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (V) Log.d(TAG,"Bluetooth service connected");
+ mService = (BluetoothA2dpSink) proxy;
+ // We just bound to the service, so refresh the UI for any connected A2DP devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ Log.w(TAG, "A2dpSinkProfile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ }
+ device.onProfileStateChanged(A2dpSinkProfile.this, BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+ mIsProfileReady=true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (V) Log.d(TAG,"Bluetooth service disconnected");
+ mIsProfileReady=false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ A2dpSinkProfile(Context context, LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ mLocalAdapter.getProfileProxy(context, new A2dpSinkServiceListener(),
+ BluetoothProfile.A2DP_SINK);
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) return new ArrayList<BluetoothDevice>(0);
+ return mService.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ if (mService == null) return false;
+ List<BluetoothDevice> srcs = getConnectedDevices();
+ if (srcs != null) {
+ for (BluetoothDevice src : srcs) {
+ if (src.equals(device)) {
+ // Connect to same device, Ignore it
+ Log.d(TAG,"Ignoring Connect");
+ return true;
+ }
+ }
+ for (BluetoothDevice src : srcs) {
+ mService.disconnect(src);
+ }
+ }
+ return mService.connect(device);
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) return false;
+ // Downgrade priority as user is disconnecting the headset.
+ if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ return mService.disconnect(device);
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+ return mService.getPriority(device);
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ if (mService == null) return;
+ if (preferred) {
+ if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ } else {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ }
+ }
+
+ boolean isA2dpPlaying() {
+ if (mService == null) return false;
+ List<BluetoothDevice> srcs = mService.getConnectedDevices();
+ if (!srcs.isEmpty()) {
+ if (mService.isA2dpPlaying(srcs.get(0))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ // we need to have same string in UI for even SINK Media Audio.
+ return R.string.bluetooth_profile_a2dp;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_a2dp_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_a2dp_profile_summary_connected;
+
+ default:
+ return Utils.getConnectionStateSummary(state);
+ }
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_headphones_a2dp;
+ }
+
+ protected void finalize() {
+ if (V) Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP_SINK,
+ mService);
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up A2DP proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index d994841..7ee53a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -851,7 +851,8 @@
case BluetoothProfile.STATE_DISCONNECTED:
if (profile.isProfileReady()) {
- if (profile instanceof A2dpProfile) {
+ if ((profile instanceof A2dpProfile)||
+ (profile instanceof A2dpSinkProfile)){
a2dpNotConnected = true;
} else if (profile instanceof HeadsetProfile) {
headsetNotConnected = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
old mode 100644
new mode 100755
index f935f31..9c5abf3
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -162,6 +162,10 @@
if (a2dp != null && a2dp.isA2dpPlaying()) {
return;
}
+ A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
+ if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
+ return;
+ }
}
if (mAdapter.startDiscovery()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
old mode 100644
new mode 100755
index 8f5e1f1..b05e34c
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -17,6 +17,7 @@
package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothMap;
@@ -73,6 +74,7 @@
private final BluetoothEventManager mEventManager;
private A2dpProfile mA2dpProfile;
+ private A2dpSinkProfile mA2dpSinkProfile;
private HeadsetProfile mHeadsetProfile;
private MapProfile mMapProfile;
private final HidProfile mHidProfile;
@@ -136,10 +138,10 @@
* @param uuids
*/
void updateLocalProfiles(ParcelUuid[] uuids) {
- // A2DP
+ // A2DP SRC
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
if (mA2dpProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local A2DP profile");
+ if(DEBUG) Log.d(TAG, "Adding local A2DP SRC profile");
mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
addProfile(mA2dpProfile, A2dpProfile.NAME,
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
@@ -148,6 +150,17 @@
Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
}
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
+ if (mA2dpSinkProfile == null) {
+ if(DEBUG) Log.d(TAG, "Adding local A2DP Sink profile");
+ mA2dpSinkProfile = new A2dpSinkProfile(mContext, mLocalAdapter, mDeviceManager, this);
+ addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME,
+ BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ } else if (mA2dpSinkProfile != null) {
+ Log.w(TAG, "Warning: A2DP Sink profile was previously added but the UUID is now missing.");
+ }
+
// Headset / Handsfree
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
@@ -288,6 +301,10 @@
if (profile != null) {
return profile.isProfileReady();
}
+ profile = mA2dpSinkProfile;
+ if (profile != null) {
+ return profile.isProfileReady();
+ }
return false;
}
@@ -295,6 +312,13 @@
return mA2dpProfile;
}
+ A2dpSinkProfile getA2dpSinkProfile() {
+ if ((mA2dpSinkProfile != null)&&(mA2dpSinkProfile.isProfileReady()))
+ return mA2dpSinkProfile;
+ else
+ return null;
+ }
+
public HeadsetProfile getHeadsetProfile() {
return mHeadsetProfile;
}
@@ -345,6 +369,12 @@
removedProfiles.remove(mA2dpProfile);
}
+ if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) &&
+ mA2dpSinkProfile != null) {
+ profiles.add(mA2dpSinkProfile);
+ removedProfiles.remove(mA2dpSinkProfile);
+ }
+
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
mOppProfile != null) {
profiles.add(mOppProfile);