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