diff options
author | AnubhavGupta <anubhavg@codeaurora.org> | 2015-06-30 12:40:39 +0530 |
---|---|---|
committer | Linux Build Service Account <lnxbuild@localhost> | 2015-10-06 03:26:43 -0600 |
commit | a63a2505e74e6955d54629c488699c7f9c52cf18 (patch) | |
tree | 8bc30213e23f99a4596531c73cacc7f3968d2d38 | |
parent | 05d1af456aed6220adce73e9781c08c74e176b3e (diff) | |
download | frameworks_base-a63a2505e74e6955d54629c488699c7f9c52cf18.zip frameworks_base-a63a2505e74e6955d54629c488699c7f9c52cf18.tar.gz frameworks_base-a63a2505e74e6955d54629c488699c7f9c52cf18.tar.bz2 |
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: I9920957e0f20583e1e2d57ca76c2c0bd9dfa0bcf
-rwxr-xr-x[-rw-r--r--] | core/java/android/bluetooth/BluetoothA2dpSink.java | 83 | ||||
-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 | ||||
-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 | ||||
-rwxr-xr-x[-rw-r--r--] | packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java | 4 | ||||
-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 |
9 files changed, 379 insertions, 3 deletions
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index 2e27345..74302f2 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/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java index 54bf4af..4a38287 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 b7c6476..774a1ec 100644..100755 --- a/core/java/android/bluetooth/IBluetoothA2dpSink.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl @@ -30,5 +30,8 @@ interface IBluetoothA2dpSink { List<BluetoothDevice> getConnectedDevices(); List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); int getConnectionState(in BluetoothDevice device); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); + boolean isA2dpPlaying(in BluetoothDevice device); BluetoothAudioConfig getAudioConfig(in BluetoothDevice device); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fe95864..54f2e1e 100644..100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7230,6 +7230,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 @@ -7330,6 +7333,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 0000000..fd76d81 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2011 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/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java index 8dec86a..0bd8dbd 100644..100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java @@ -112,11 +112,15 @@ public final class BluetoothDeviceFilter { if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) { return true; } + if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS)) { + return true; + } if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) { return true; } } else if (btClass != null) { if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) || + btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP_SINK) || btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index b0429ef..ac5a4ab 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 0380e21..8300e0e 100644..100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -157,6 +157,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 f018b24..6a7890f 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; @@ -75,6 +76,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; @@ -145,10 +147,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); @@ -157,6 +159,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)) { @@ -297,6 +310,10 @@ public final class LocalBluetoothProfileManager { if (profile != null) { return profile.isProfileReady(); } + profile = mA2dpSinkProfile; + if (profile != null) { + return profile.isProfileReady(); + } return false; } @@ -304,6 +321,13 @@ public final class LocalBluetoothProfileManager { return mA2dpProfile; } + A2dpSinkProfile getA2dpSinkProfile() { + if ((mA2dpSinkProfile != null)&&(mA2dpSinkProfile.isProfileReady())) + return mA2dpSinkProfile; + else + return null; + } + public HeadsetProfile getHeadsetProfile() { return mHeadsetProfile; } @@ -354,6 +378,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); |