diff options
| -rwxr-xr-x[-rw-r--r--] | core/java/android/bluetooth/BluetoothA2dpSink.java | 83 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothAvrcpController.java | 208 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java | 189 | ||||
| -rwxr-xr-x[-rw-r--r--] | core/java/android/bluetooth/BluetoothClass.java | 17 | ||||
| -rwxr-xr-x[-rw-r--r--] | core/java/android/bluetooth/IBluetoothA2dpSink.aidl | 3 | ||||
| -rw-r--r-- | core/java/android/bluetooth/IBluetoothAvrcpController.aidl | 8 | ||||
| -rwxr-xr-x[-rw-r--r--] | core/java/android/provider/Settings.java | 11 | ||||
| -rwxr-xr-x | packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java | 223 | ||||
| -rw-r--r-- | packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java | 3 | ||||
| -rwxr-xr-x[-rw-r--r--] | packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java | 4 | ||||
| -rwxr-xr-x[-rw-r--r--] | packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java | 34 |
12 files changed, 773 insertions, 29 deletions
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index 2e273459add8..74302f27ec1d 100644..100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -371,6 +371,89 @@ public final class BluetoothA2dpSink implements BluetoothProfile { } /** + * 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 b53a8fc0f6fc..444e4293fe2b 100644 --- a/core/java/android/bluetooth/BluetoothAvrcpController.java +++ b/core/java/android/bluetooth/BluetoothAvrcpController.java @@ -20,6 +20,8 @@ import android.content.ComponentName; 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.ArrayList; 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 @@ import java.util.List; */ 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 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * 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 @@ public final class BluetoothAvrcpController implements BluetoothProfile { 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 @@ public final class BluetoothAvrcpController implements BluetoothProfile { 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 000000000000..590fd63eda8c --- /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 000000000000..927cb5666515 --- /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 index 54bf4afa93ff..4a38287e7ca9 100644..100755 --- a/core/java/android/bluetooth/BluetoothClass.java +++ b/core/java/android/bluetooth/BluetoothClass.java @@ -283,6 +283,8 @@ public final class BluetoothClass implements Parcelable { 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 @@ public final class BluetoothClass implements Parcelable { 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 index b7c647677000..d1458246dfe9 100644..100755 --- a/core/java/android/bluetooth/IBluetoothA2dpSink.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl @@ -31,4 +31,7 @@ interface IBluetoothA2dpSink { 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 f917a50860b7..f1288d022290 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 @@ interface IBluetoothAvrcpController { 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 index 746e11066d9b..bae941d8b0ea 100644..100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7227,6 +7227,9 @@ public final class Settings { 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 @@ -7327,6 +7330,14 @@ public final class Settings { } /** + * 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 000000000000..77f2e19abd97 --- /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 e4b1ed8604ec..147c3e27108b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -846,7 +846,8 @@ public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDe 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 index f935f31cad8e..9c5abf3a00dd 100644..100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -162,6 +162,10 @@ public final class LocalBluetoothAdapter { 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 index 8f5e1f199fad..b05e34c02ef6 100644..100755 --- 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 @@ public final class LocalBluetoothProfileManager { private final BluetoothEventManager mEventManager; private A2dpProfile mA2dpProfile; + private A2dpSinkProfile mA2dpSinkProfile; private HeadsetProfile mHeadsetProfile; private MapProfile mMapProfile; private final HidProfile mHidProfile; @@ -136,10 +138,10 @@ public final class LocalBluetoothProfileManager { * @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 @@ public final class LocalBluetoothProfileManager { 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 @@ public final class LocalBluetoothProfileManager { if (profile != null) { return profile.isProfileReady(); } + profile = mA2dpSinkProfile; + if (profile != null) { + return profile.isProfileReady(); + } return false; } @@ -295,6 +312,13 @@ public final class LocalBluetoothProfileManager { return mA2dpProfile; } + A2dpSinkProfile getA2dpSinkProfile() { + if ((mA2dpSinkProfile != null)&&(mA2dpSinkProfile.isProfileReady())) + return mA2dpSinkProfile; + else + return null; + } + public HeadsetProfile getHeadsetProfile() { return mHeadsetProfile; } @@ -345,6 +369,12 @@ public final class LocalBluetoothProfileManager { 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); |