1 /* 2 * Copyright 2018 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 com.android.bluetooth.avrcp; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.IBluetoothAvrcpTarget; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.media.AudioManager; 27 import android.os.Looper; 28 import android.os.SystemProperties; 29 import android.os.UserManager; 30 import android.util.Log; 31 32 import com.android.bluetooth.BluetoothMetricsProto; 33 import com.android.bluetooth.Utils; 34 import com.android.bluetooth.a2dp.A2dpService; 35 import com.android.bluetooth.btservice.MetricsLogger; 36 import com.android.bluetooth.btservice.ProfileService; 37 import com.android.bluetooth.btservice.ServiceFactory; 38 39 import java.util.List; 40 import java.util.Objects; 41 42 /** 43 * Provides Bluetooth AVRCP Target profile as a service in the Bluetooth application. 44 * @hide 45 */ 46 public class AvrcpTargetService extends ProfileService { 47 private static final String TAG = "AvrcpTargetService"; 48 private static final boolean DEBUG = true; 49 private static final String AVRCP_ENABLE_PROPERTY = "persist.bluetooth.enablenewavrcp"; 50 51 private static final int AVRCP_MAX_VOL = 127; 52 private static int sDeviceMaxVolume = 0; 53 54 private MediaPlayerList mMediaPlayerList; 55 private AudioManager mAudioManager; 56 private AvrcpBroadcastReceiver mReceiver; 57 private AvrcpNativeInterface mNativeInterface; 58 private AvrcpVolumeManager mVolumeManager; 59 private ServiceFactory mFactory = new ServiceFactory(); 60 61 // Only used to see if the metadata has changed from its previous value 62 private MediaData mCurrentData; 63 64 private static AvrcpTargetService sInstance = null; 65 66 class ListCallback implements MediaPlayerList.MediaUpdateCallback, 67 MediaPlayerList.FolderUpdateCallback { 68 @Override run(MediaData data)69 public void run(MediaData data) { 70 boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata); 71 boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state); 72 boolean queue = !Objects.equals(mCurrentData.queue, data.queue); 73 74 if (DEBUG) { 75 Log.d(TAG, "onMediaUpdated: track_changed=" + metadata 76 + " state=" + state + " queue=" + queue); 77 } 78 mCurrentData = data; 79 80 mNativeInterface.sendMediaUpdate(metadata, state, queue); 81 } 82 83 @Override run(boolean availablePlayers, boolean addressedPlayers, boolean uids)84 public void run(boolean availablePlayers, boolean addressedPlayers, 85 boolean uids) { 86 mNativeInterface.sendFolderUpdate(availablePlayers, addressedPlayers, uids); 87 } 88 } 89 90 private class AvrcpBroadcastReceiver extends BroadcastReceiver { 91 @Override onReceive(Context context, Intent intent)92 public void onReceive(Context context, Intent intent) { 93 String action = intent.getAction(); 94 if (action.equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { 95 if (mNativeInterface == null) return; 96 97 // Update all the playback status info for each connected device 98 mNativeInterface.sendMediaUpdate(false, true, false); 99 } 100 } 101 } 102 103 /** 104 * Get the AvrcpTargetService instance. Returns null if the service hasn't been initialized. 105 */ get()106 public static AvrcpTargetService get() { 107 return sInstance; 108 } 109 110 @Override getName()111 public String getName() { 112 return TAG; 113 } 114 115 @Override initBinder()116 protected IProfileServiceBinder initBinder() { 117 return new AvrcpTargetBinder(this); 118 } 119 120 @Override setUserUnlocked(int userId)121 protected void setUserUnlocked(int userId) { 122 Log.i(TAG, "User unlocked, initializing the service"); 123 124 if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) { 125 Log.w(TAG, "Skipping initialization of the new AVRCP Target Player List"); 126 sInstance = null; 127 return; 128 } 129 130 if (mMediaPlayerList != null) { 131 mMediaPlayerList.init(new ListCallback()); 132 } 133 } 134 135 @Override start()136 protected boolean start() { 137 if (sInstance != null) { 138 Log.wtfStack(TAG, "The service has already been initialized"); 139 return false; 140 } 141 142 Log.i(TAG, "Starting the AVRCP Target Service"); 143 mCurrentData = new MediaData(null, null, null); 144 145 if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) { 146 Log.w(TAG, "Skipping initialization of the new AVRCP Target Service"); 147 sInstance = null; 148 return true; 149 } 150 151 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 152 sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 153 154 mMediaPlayerList = new MediaPlayerList(Looper.myLooper(), this); 155 156 mNativeInterface = AvrcpNativeInterface.getInterface(); 157 mNativeInterface.init(AvrcpTargetService.this); 158 159 mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface); 160 161 UserManager userManager = UserManager.get(getApplicationContext()); 162 if (userManager.isUserUnlocked()) { 163 mMediaPlayerList.init(new ListCallback()); 164 } 165 166 mReceiver = new AvrcpBroadcastReceiver(); 167 IntentFilter filter = new IntentFilter(); 168 filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 169 registerReceiver(mReceiver, filter); 170 171 // Only allow the service to be used once it is initialized 172 sInstance = this; 173 174 return true; 175 } 176 177 @Override stop()178 protected boolean stop() { 179 Log.i(TAG, "Stopping the AVRCP Target Service"); 180 181 if (sInstance == null) { 182 Log.w(TAG, "stop() called before start()"); 183 return true; 184 } 185 186 sInstance = null; 187 unregisterReceiver(mReceiver); 188 189 // We check the interfaces first since they only get set on User Unlocked 190 if (mMediaPlayerList != null) mMediaPlayerList.cleanup(); 191 if (mNativeInterface != null) mNativeInterface.cleanup(); 192 193 mMediaPlayerList = null; 194 mNativeInterface = null; 195 mAudioManager = null; 196 mReceiver = null; 197 return true; 198 } 199 init()200 private void init() { 201 } 202 deviceConnected(BluetoothDevice device, boolean absoluteVolume)203 void deviceConnected(BluetoothDevice device, boolean absoluteVolume) { 204 Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume); 205 mVolumeManager.deviceConnected(device, absoluteVolume); 206 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP); 207 } 208 deviceDisconnected(BluetoothDevice device)209 void deviceDisconnected(BluetoothDevice device) { 210 Log.i(TAG, "deviceDisconnected: device=" + device); 211 mVolumeManager.deviceDisconnected(device); 212 } 213 214 /** 215 * Signal to the service that the current audio out device has changed and to inform 216 * the audio service whether the new device supports absolute volume. If it does, also 217 * set the absolute volume level on the remote device. 218 */ volumeDeviceSwitched(BluetoothDevice device)219 public void volumeDeviceSwitched(BluetoothDevice device) { 220 if (DEBUG) { 221 Log.d(TAG, "volumeDeviceSwitched: device=" + device); 222 } 223 mVolumeManager.volumeDeviceSwitched(device); 224 } 225 226 /** 227 * Store the current system volume for a device in order to be retrieved later. 228 */ storeVolumeForDevice(BluetoothDevice device)229 public void storeVolumeForDevice(BluetoothDevice device) { 230 if (device == null) return; 231 232 List<BluetoothDevice> HAActiveDevices = null; 233 if (mFactory.getHearingAidService() != null) { 234 HAActiveDevices = mFactory.getHearingAidService().getActiveDevices(); 235 } 236 if (HAActiveDevices != null 237 && (HAActiveDevices.get(0) != null || HAActiveDevices.get(1) != null)) { 238 Log.d(TAG, "Do not store volume when Hearing Aid devices is active"); 239 return; 240 } 241 mVolumeManager.storeVolumeForDevice(device); 242 } 243 244 /** 245 * Remove the stored volume for a device. 246 */ removeStoredVolumeForDevice(BluetoothDevice device)247 public void removeStoredVolumeForDevice(BluetoothDevice device) { 248 if (device == null) return; 249 250 mVolumeManager.removeStoredVolumeForDevice(device); 251 } 252 253 /** 254 * Retrieve the remembered volume for a device. Returns -1 if there is no volume for the 255 * device. 256 */ getRememberedVolumeForDevice(BluetoothDevice device)257 public int getRememberedVolumeForDevice(BluetoothDevice device) { 258 if (device == null) return -1; 259 260 return mVolumeManager.getVolume(device, mVolumeManager.getNewDeviceVolume()); 261 } 262 263 // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly. setVolume(int avrcpVolume)264 void setVolume(int avrcpVolume) { 265 int deviceVolume = 266 (int) Math.floor((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL); 267 if (DEBUG) { 268 Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume 269 + " deviceVolume=" + deviceVolume 270 + " sDeviceMaxVolume=" + sDeviceMaxVolume); 271 } 272 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, deviceVolume, 273 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); 274 } 275 276 /** 277 * Set the volume on the remote device. Does nothing if the device doesn't support absolute 278 * volume. 279 */ sendVolumeChanged(int deviceVolume)280 public void sendVolumeChanged(int deviceVolume) { 281 int avrcpVolume = 282 (int) Math.floor((double) deviceVolume * AVRCP_MAX_VOL / sDeviceMaxVolume); 283 if (avrcpVolume > 127) avrcpVolume = 127; 284 if (DEBUG) { 285 Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume 286 + " deviceVolume=" + deviceVolume 287 + " sDeviceMaxVolume=" + sDeviceMaxVolume); 288 } 289 mNativeInterface.sendVolumeChanged(avrcpVolume); 290 } 291 getCurrentSongInfo()292 Metadata getCurrentSongInfo() { 293 return mMediaPlayerList.getCurrentSongInfo(); 294 } 295 getPlayState()296 PlayStatus getPlayState() { 297 return PlayStatus.fromPlaybackState(mMediaPlayerList.getCurrentPlayStatus(), 298 Long.parseLong(getCurrentSongInfo().duration)); 299 } 300 getCurrentMediaId()301 String getCurrentMediaId() { 302 String id = mMediaPlayerList.getCurrentMediaId(); 303 if (id != null) return id; 304 305 Metadata song = getCurrentSongInfo(); 306 if (song != null) return song.mediaId; 307 308 // We always want to return something, the error string just makes debugging easier 309 return "error"; 310 } 311 getNowPlayingList()312 List<Metadata> getNowPlayingList() { 313 return mMediaPlayerList.getNowPlayingList(); 314 } 315 getCurrentPlayerId()316 int getCurrentPlayerId() { 317 return mMediaPlayerList.getCurrentPlayerId(); 318 } 319 320 // TODO (apanicke): Have the Player List also contain info about the play state of each player getMediaPlayerList()321 List<PlayerInfo> getMediaPlayerList() { 322 return mMediaPlayerList.getMediaPlayerList(); 323 } 324 getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb)325 void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) { 326 mMediaPlayerList.getPlayerRoot(playerId, cb); 327 } 328 getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb)329 void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) { 330 mMediaPlayerList.getFolderItems(playerId, mediaId, cb); 331 } 332 playItem(int playerId, boolean nowPlaying, String mediaId)333 void playItem(int playerId, boolean nowPlaying, String mediaId) { 334 // NOTE: playerId isn't used if nowPlaying is true, since its assumed to be the current 335 // active player 336 mMediaPlayerList.playItem(playerId, nowPlaying, mediaId); 337 } 338 339 // TODO (apanicke): Handle key events here in the service. Currently it was more convenient to 340 // handle them there but logically they make more sense handled here. sendMediaKeyEvent(int event, boolean pushed)341 void sendMediaKeyEvent(int event, boolean pushed) { 342 if (DEBUG) Log.d(TAG, "getMediaKeyEvent: event=" + event + " pushed=" + pushed); 343 mMediaPlayerList.sendMediaKeyEvent(event, pushed); 344 } 345 setActiveDevice(BluetoothDevice device)346 void setActiveDevice(BluetoothDevice device) { 347 Log.i(TAG, "setActiveDevice: device=" + device); 348 if (device == null) { 349 Log.wtfStack(TAG, "setActiveDevice: could not find device " + device); 350 } 351 A2dpService.getA2dpService().setActiveDevice(device); 352 } 353 354 /** 355 * Dump debugging information to the string builder 356 */ dump(StringBuilder sb)357 public void dump(StringBuilder sb) { 358 sb.append("\nProfile: AvrcpTargetService:\n"); 359 if (sInstance == null) { 360 sb.append("AvrcpTargetService not running"); 361 return; 362 } 363 364 StringBuilder tempBuilder = new StringBuilder(); 365 if (mMediaPlayerList != null) { 366 mMediaPlayerList.dump(tempBuilder); 367 } else { 368 tempBuilder.append("\nMedia Player List is empty\n"); 369 } 370 371 mVolumeManager.dump(tempBuilder); 372 373 // Tab everything over by two spaces 374 sb.append(tempBuilder.toString().replaceAll("(?m)^", " ")); 375 } 376 377 private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub 378 implements IProfileServiceBinder { 379 private AvrcpTargetService mService; 380 AvrcpTargetBinder(AvrcpTargetService service)381 AvrcpTargetBinder(AvrcpTargetService service) { 382 mService = service; 383 } 384 385 @Override cleanup()386 public void cleanup() { 387 mService = null; 388 } 389 390 @Override sendVolumeChanged(int volume)391 public void sendVolumeChanged(int volume) { 392 if (!Utils.checkCaller()) { 393 Log.w(TAG, "sendVolumeChanged not allowed for non-active user"); 394 return; 395 } 396 397 if (mService == null) { 398 return; 399 } 400 401 mService.sendVolumeChanged(volume); 402 } 403 } 404 } 405