Merge "Add support for AVRCP 1.3."

am: 168f2e556b

* commit '168f2e556b7222fd0dab7eadc4ceee2eca13be1a':
  Add support for AVRCP 1.3.
This commit is contained in:
Sanket Agarwal
2016-01-20 01:25:04 +00:00
committed by android-build-merger
12 changed files with 773 additions and 29 deletions

83
core/java/android/bluetooth/BluetoothA2dpSink.java Normal file → Executable file
View File

@@ -370,6 +370,89 @@ public final class BluetoothA2dpSink implements BluetoothProfile {
return null; return null;
} }
/**
* 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. * Helper for converting a state to a string.
* *

View File

@@ -20,6 +20,8 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.media.MediaMetadata;
import android.media.session.PlaybackState;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
@@ -28,8 +30,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* This class provides the public APIs to control the Bluetooth AVRCP Controller * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
* profile. * supports player information, playback support and track metadata.
* *
*<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
@@ -39,7 +41,7 @@ import java.util.List;
*/ */
public final class BluetoothAvrcpController implements BluetoothProfile { public final class BluetoothAvrcpController implements BluetoothProfile {
private static final String TAG = "BluetoothAvrcpController"; private static final String TAG = "BluetoothAvrcpController";
private static final boolean DBG = true; private static final boolean DBG = false;
private static final boolean VDBG = false; private static final boolean VDBG = false;
/** /**
@@ -61,7 +63,63 @@ public final class BluetoothAvrcpController implements BluetoothProfile {
* receive. * receive.
*/ */
public static final String ACTION_CONNECTION_STATE_CHANGED = 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 Context mContext;
private ServiceListener mServiceListener; private ServiceListener mServiceListener;
@@ -69,33 +127,33 @@ public final class BluetoothAvrcpController implements BluetoothProfile {
private BluetoothAdapter mAdapter; private BluetoothAdapter mAdapter;
final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
new IBluetoothStateChangeCallback.Stub() { new IBluetoothStateChangeCallback.Stub() {
public void onBluetoothStateChange(boolean up) { public void onBluetoothStateChange(boolean up) {
if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
if (!up) { if (!up) {
if (VDBG) Log.d(TAG,"Unbinding service..."); if (VDBG) Log.d(TAG,"Unbinding service...");
synchronized (mConnection) { synchronized (mConnection) {
try { try {
mService = null; mService = null;
mContext.unbindService(mConnection); mContext.unbindService(mConnection);
} catch (Exception re) { } catch (Exception re) {
Log.e(TAG,"",re); Log.e(TAG,"",re);
}
} }
} else { }
synchronized (mConnection) { } else {
try { synchronized (mConnection) {
if (mService == null) { try {
if (VDBG) Log.d(TAG,"Binding service..."); if (mService == null) {
doBind(); if (VDBG) Log.d(TAG,"Binding service...");
} doBind();
} catch (Exception re) {
Log.e(TAG,"",re);
} }
} catch (Exception re) {
Log.e(TAG,"",re);
} }
} }
} }
}; }
};
/** /**
* Create a BluetoothAvrcpController proxy object for interacting with the local * 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"); 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() { private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected"); if (DBG) Log.d(TAG, "Proxy object connected");

View File

@@ -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;

View File

@@ -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;
}
}

17
core/java/android/bluetooth/BluetoothClass.java Normal file → Executable file
View File

@@ -283,6 +283,8 @@ public final class BluetoothClass implements Parcelable {
public static final int PROFILE_PANU = 4; public static final int PROFILE_PANU = 4;
/** @hide */ /** @hide */
public static final int PROFILE_NAP = 5; public static final int PROFILE_NAP = 5;
/** @hide */
public static final int PROFILE_A2DP_SINK = 6;
/** /**
* Check class bits for possible bluetooth profile support. * Check class bits for possible bluetooth profile support.
@@ -310,6 +312,21 @@ public final class BluetoothClass implements Parcelable {
default: default:
return false; 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) { } else if (profile == PROFILE_HEADSET) {
// The render service class is required by the spec for HFP, so is a // The render service class is required by the spec for HFP, so is a
// pretty good signal // pretty good signal

3
core/java/android/bluetooth/IBluetoothA2dpSink.aidl Normal file → Executable file
View File

@@ -31,4 +31,7 @@ interface IBluetoothA2dpSink {
List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
int getConnectionState(in BluetoothDevice device); int getConnectionState(in BluetoothDevice device);
BluetoothAudioConfig getAudioConfig(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);
} }

View File

@@ -16,7 +16,10 @@
package android.bluetooth; package android.bluetooth;
import android.bluetooth.BluetoothAvrcpPlayerSettings;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.media.MediaMetadata;
import android.media.session.PlaybackState;
/** /**
* APIs for Bluetooth AVRCP controller service * APIs for Bluetooth AVRCP controller service
@@ -28,4 +31,9 @@ interface IBluetoothAvrcpController {
List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
int getConnectionState(in BluetoothDevice device); int getConnectionState(in BluetoothDevice device);
void sendPassThroughCmd(in BluetoothDevice device, int keyCode, int keyState); 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);
} }

11
core/java/android/provider/Settings.java Normal file → Executable file
View File

@@ -7233,6 +7233,9 @@ public final class Settings {
public static final String public static final String
BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_"; BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_";
/** {@hide} */ /** {@hide} */
public static final String
BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX = "bluetooth_a2dp_src_priority_";
/** {@hide} */
public static final String public static final String
BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_"; BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_";
/** {@hide} */ /** {@hide} */
@@ -7335,6 +7338,14 @@ public final class Settings {
return BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); return BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
} }
/**
* 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. * Get the key that retrieves a bluetooth Input Device's priority.
* @hide * @hide

View File

@@ -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);
}
}
}
}

View File

@@ -851,7 +851,8 @@ public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDe
case BluetoothProfile.STATE_DISCONNECTED: case BluetoothProfile.STATE_DISCONNECTED:
if (profile.isProfileReady()) { if (profile.isProfileReady()) {
if (profile instanceof A2dpProfile) { if ((profile instanceof A2dpProfile)||
(profile instanceof A2dpSinkProfile)){
a2dpNotConnected = true; a2dpNotConnected = true;
} else if (profile instanceof HeadsetProfile) { } else if (profile instanceof HeadsetProfile) {
headsetNotConnected = true; headsetNotConnected = true;

View File

@@ -162,6 +162,10 @@ public final class LocalBluetoothAdapter {
if (a2dp != null && a2dp.isA2dpPlaying()) { if (a2dp != null && a2dp.isA2dpPlaying()) {
return; return;
} }
A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
return;
}
} }
if (mAdapter.startDiscovery()) { if (mAdapter.startDiscovery()) {

View File

@@ -17,6 +17,7 @@
package com.android.settingslib.bluetooth; package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothMap; import android.bluetooth.BluetoothMap;
@@ -73,6 +74,7 @@ public final class LocalBluetoothProfileManager {
private final BluetoothEventManager mEventManager; private final BluetoothEventManager mEventManager;
private A2dpProfile mA2dpProfile; private A2dpProfile mA2dpProfile;
private A2dpSinkProfile mA2dpSinkProfile;
private HeadsetProfile mHeadsetProfile; private HeadsetProfile mHeadsetProfile;
private MapProfile mMapProfile; private MapProfile mMapProfile;
private final HidProfile mHidProfile; private final HidProfile mHidProfile;
@@ -136,10 +138,10 @@ public final class LocalBluetoothProfileManager {
* @param uuids * @param uuids
*/ */
void updateLocalProfiles(ParcelUuid[] uuids) { void updateLocalProfiles(ParcelUuid[] uuids) {
// A2DP // A2DP SRC
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
if (mA2dpProfile == null) { 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); mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
addProfile(mA2dpProfile, A2dpProfile.NAME, addProfile(mA2dpProfile, A2dpProfile.NAME,
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 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."); 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 // Headset / Handsfree
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) || if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
@@ -288,6 +301,10 @@ public final class LocalBluetoothProfileManager {
if (profile != null) { if (profile != null) {
return profile.isProfileReady(); return profile.isProfileReady();
} }
profile = mA2dpSinkProfile;
if (profile != null) {
return profile.isProfileReady();
}
return false; return false;
} }
@@ -295,6 +312,13 @@ public final class LocalBluetoothProfileManager {
return mA2dpProfile; return mA2dpProfile;
} }
A2dpSinkProfile getA2dpSinkProfile() {
if ((mA2dpSinkProfile != null)&&(mA2dpSinkProfile.isProfileReady()))
return mA2dpSinkProfile;
else
return null;
}
public HeadsetProfile getHeadsetProfile() { public HeadsetProfile getHeadsetProfile() {
return mHeadsetProfile; return mHeadsetProfile;
} }
@@ -345,6 +369,12 @@ public final class LocalBluetoothProfileManager {
removedProfiles.remove(mA2dpProfile); removedProfiles.remove(mA2dpProfile);
} }
if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) &&
mA2dpSinkProfile != null) {
profiles.add(mA2dpSinkProfile);
removedProfiles.remove(mA2dpSinkProfile);
}
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
mOppProfile != null) { mOppProfile != null) {
profiles.add(mOppProfile); profiles.add(mOppProfile);