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