1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.bluetooth; 18 19 import static android.bluetooth.BluetoothUtils.getSyncTimeout; 20 21 import android.annotation.RequiresPermission; 22 import android.annotation.SdkConstant; 23 import android.annotation.SdkConstant.SdkConstantType; 24 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 25 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 26 import android.content.AttributionSource; 27 import android.content.Context; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import com.android.modules.utils.SynchronousResultReceiver; 33 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.concurrent.TimeoutException; 37 38 /** 39 * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently 40 * supports player information, playback support and track metadata. 41 * 42 * <p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP 43 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 44 * the BluetoothAvrcpController proxy object. 45 * 46 * {@hide} 47 */ 48 public final class BluetoothAvrcpController implements BluetoothProfile { 49 private static final String TAG = "BluetoothAvrcpController"; 50 private static final boolean DBG = false; 51 private static final boolean VDBG = false; 52 53 /** 54 * Intent used to broadcast the change in connection state of the AVRCP Controller 55 * profile. 56 * 57 * <p>This intent will have 3 extras: 58 * <ul> 59 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 60 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 61 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 62 * </ul> 63 * 64 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 65 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 66 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 67 */ 68 @RequiresLegacyBluetoothPermission 69 @RequiresBluetoothConnectPermission 70 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 71 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 72 public static final String ACTION_CONNECTION_STATE_CHANGED = 73 "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED"; 74 75 /** 76 * Intent used to broadcast the change in player application setting state on AVRCP AG. 77 * 78 * <p>This intent will have the following extras: 79 * <ul> 80 * <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the 81 * most recent player setting. </li> 82 * </ul> 83 */ 84 @RequiresBluetoothConnectPermission 85 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 86 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 87 public static final String ACTION_PLAYER_SETTING = 88 "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING"; 89 90 public static final String EXTRA_PLAYER_SETTING = 91 "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING"; 92 93 private final BluetoothAdapter mAdapter; 94 private final AttributionSource mAttributionSource; 95 private final BluetoothProfileConnector<IBluetoothAvrcpController> mProfileConnector = 96 new BluetoothProfileConnector(this, BluetoothProfile.AVRCP_CONTROLLER, 97 "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) { 98 @Override 99 public IBluetoothAvrcpController getServiceInterface(IBinder service) { 100 return IBluetoothAvrcpController.Stub.asInterface(service); 101 } 102 }; 103 104 /** 105 * Create a BluetoothAvrcpController proxy object for interacting with the local 106 * Bluetooth AVRCP service. 107 */ BluetoothAvrcpController(Context context, ServiceListener listener, BluetoothAdapter adapter)108 /* package */ BluetoothAvrcpController(Context context, ServiceListener listener, 109 BluetoothAdapter adapter) { 110 mAdapter = adapter; 111 mAttributionSource = adapter.getAttributionSource(); 112 mProfileConnector.connect(context, listener); 113 } 114 close()115 /*package*/ void close() { 116 mProfileConnector.disconnect(); 117 } 118 getService()119 private IBluetoothAvrcpController getService() { 120 return mProfileConnector.getService(); 121 } 122 123 @Override finalize()124 public void finalize() { 125 close(); 126 } 127 128 /** 129 * {@inheritDoc} 130 */ 131 @Override 132 @RequiresBluetoothConnectPermission 133 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()134 public List<BluetoothDevice> getConnectedDevices() { 135 if (VDBG) log("getConnectedDevices()"); 136 final IBluetoothAvrcpController service = getService(); 137 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 138 if (service == null) { 139 Log.w(TAG, "Proxy not attached to service"); 140 if (DBG) log(Log.getStackTraceString(new Throwable())); 141 } else if (isEnabled()) { 142 try { 143 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 144 SynchronousResultReceiver.get(); 145 service.getConnectedDevices(mAttributionSource, recv); 146 return Attributable.setAttributionSource( 147 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 148 mAttributionSource); 149 } catch (RemoteException | TimeoutException e) { 150 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 151 } 152 } 153 return defaultValue; 154 } 155 156 /** 157 * {@inheritDoc} 158 */ 159 @Override 160 @RequiresBluetoothConnectPermission 161 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)162 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 163 if (VDBG) log("getDevicesMatchingStates()"); 164 final IBluetoothAvrcpController service = getService(); 165 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 166 if (service == null) { 167 Log.w(TAG, "Proxy not attached to service"); 168 if (DBG) log(Log.getStackTraceString(new Throwable())); 169 } else if (isEnabled()) { 170 try { 171 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 172 SynchronousResultReceiver.get(); 173 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); 174 return Attributable.setAttributionSource( 175 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 176 mAttributionSource); 177 } catch (RemoteException | TimeoutException e) { 178 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 179 } 180 } 181 return defaultValue; 182 } 183 184 /** 185 * {@inheritDoc} 186 */ 187 @Override 188 @RequiresBluetoothConnectPermission 189 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)190 public int getConnectionState(BluetoothDevice device) { 191 if (VDBG) log("getState(" + device + ")"); 192 final IBluetoothAvrcpController service = getService(); 193 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 194 if (service == null) { 195 Log.w(TAG, "Proxy not attached to service"); 196 if (DBG) log(Log.getStackTraceString(new Throwable())); 197 } else if (isEnabled() && isValidDevice(device)) { 198 try { 199 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 200 service.getConnectionState(device, mAttributionSource, recv); 201 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 202 } catch (RemoteException | TimeoutException e) { 203 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 204 } 205 } 206 return defaultValue; 207 } 208 209 /** 210 * Gets the player application settings. 211 * 212 * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error. 213 */ 214 @RequiresBluetoothConnectPermission 215 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getPlayerSettings(BluetoothDevice device)216 public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { 217 if (DBG) Log.d(TAG, "getPlayerSettings"); 218 BluetoothAvrcpPlayerSettings settings = null; 219 final IBluetoothAvrcpController service = getService(); 220 final BluetoothAvrcpPlayerSettings defaultValue = null; 221 if (service == null) { 222 Log.w(TAG, "Proxy not attached to service"); 223 if (DBG) log(Log.getStackTraceString(new Throwable())); 224 } else if (isEnabled()) { 225 try { 226 final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv = 227 SynchronousResultReceiver.get(); 228 service.getPlayerSettings(device, mAttributionSource, recv); 229 settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 230 } catch (RemoteException | TimeoutException e) { 231 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 232 } 233 } 234 return defaultValue; 235 } 236 237 /** 238 * Sets the player app setting for current player. 239 * returns true in case setting is supported by remote, false otherwise 240 */ 241 @RequiresBluetoothConnectPermission 242 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting)243 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { 244 if (DBG) Log.d(TAG, "setPlayerApplicationSetting"); 245 final IBluetoothAvrcpController service = getService(); 246 final boolean defaultValue = false; 247 if (service == null) { 248 Log.w(TAG, "Proxy not attached to service"); 249 if (DBG) log(Log.getStackTraceString(new Throwable())); 250 } else if (isEnabled()) { 251 try { 252 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 253 service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv); 254 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 255 } catch (RemoteException | TimeoutException e) { 256 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 257 } 258 } 259 return defaultValue; 260 } 261 262 /** 263 * Send Group Navigation Command to Remote. 264 * possible keycode values: next_grp, previous_grp defined above 265 */ 266 @RequiresBluetoothConnectPermission 267 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState)268 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { 269 Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " 270 + keyState); 271 final IBluetoothAvrcpController service = getService(); 272 if (service == null) { 273 Log.w(TAG, "Proxy not attached to service"); 274 if (DBG) log(Log.getStackTraceString(new Throwable())); 275 } else if (isEnabled()) { 276 try { 277 final SynchronousResultReceiver recv = SynchronousResultReceiver.get(); 278 service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv); 279 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); 280 return; 281 } catch (RemoteException | TimeoutException e) { 282 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 283 } 284 } 285 } 286 isEnabled()287 private boolean isEnabled() { 288 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 289 } 290 isValidDevice(BluetoothDevice device)291 private static boolean isValidDevice(BluetoothDevice device) { 292 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 293 } 294 log(String msg)295 private static void log(String msg) { 296 Log.d(TAG, msg); 297 } 298 } 299