Merge "Add support for AVRCP 1.3."
am: 168f2e556b
* commit '168f2e556b7222fd0dab7eadc4ceee2eca13be1a':
Add support for AVRCP 1.3.
This commit is contained in:
83
core/java/android/bluetooth/BluetoothA2dpSink.java
Normal file → Executable file
83
core/java/android/bluetooth/BluetoothA2dpSink.java
Normal file → Executable file
@@ -370,6 +370,89 @@ public final class BluetoothA2dpSink implements BluetoothProfile {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
189
core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java
Normal file
189
core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java
Normal 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
17
core/java/android/bluetooth/BluetoothClass.java
Normal file → Executable file
@@ -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
|
||||
|
||||
3
core/java/android/bluetooth/IBluetoothA2dpSink.aidl
Normal file → Executable file
3
core/java/android/bluetooth/IBluetoothA2dpSink.aidl
Normal file → Executable file
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
11
core/java/android/provider/Settings.java
Normal file → Executable file
11
core/java/android/provider/Settings.java
Normal file → Executable file
@@ -7233,6 +7233,9 @@ public final class Settings {
|
||||
public static final String
|
||||
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} */
|
||||
@@ -7335,6 +7338,14 @@ public final class Settings {
|
||||
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.
|
||||
* @hide
|
||||
|
||||
223
packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
Executable file
223
packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -851,7 +851,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;
|
||||
|
||||
4
packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
Normal file → Executable file
4
packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
Normal file → Executable file
@@ -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()) {
|
||||
|
||||
34
packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
Normal file → Executable file
34
packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
Normal file → Executable file
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user