AvrcpTargetService.java revision 0ad53ef457f156314595a27c63660454450d3c84
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.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 58 // Only used to see if the metadata has changed from its previous value 59 private MediaData mCurrentData; 60 61 private static AvrcpTargetService sInstance = null; 62 63 private class ListCallback implements MediaPlayerList.MediaUpdateCallback { 64 @Override 65 public void run(MediaData data) { 66 boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata); 67 boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state); 68 boolean queue = !Objects.equals(mCurrentData.queue, data.queue); 69 70 if (DEBUG) { 71 Log.d(TAG, "onMediaUpdated: track_changed=" + metadata 72 + " state=" + state + " queue=" + queue); 73 } 74 mCurrentData = data; 75 76 mNativeInterface.sendMediaUpdate(metadata, state, queue); 77 } 78 } 79 80 private class AvrcpBroadcastReceiver extends BroadcastReceiver { 81 @Override 82 public void onReceive(Context context, Intent intent) { 83 String action = intent.getAction(); 84 if (action.equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { 85 if (mNativeInterface == null) return; 86 87 // Update all the playback status info for each connected device 88 mNativeInterface.sendMediaUpdate(false, true, false); 89 } 90 } 91 } 92 93 /** 94 * Get the AvrcpTargetService instance. Returns null if the service hasn't been initialized. 95 */ 96 public static AvrcpTargetService get() { 97 return sInstance; 98 } 99 100 @Override 101 public String getName() { 102 return TAG; 103 } 104 105 @Override 106 protected IProfileServiceBinder initBinder() { 107 return new AvrcpTargetBinder(this); 108 } 109 110 @Override 111 protected void setUserUnlocked(int userId) { 112 Log.i(TAG, "User unlocked, initializing the service"); 113 114 if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, false)) { 115 Log.w(TAG, "Skipping initialization of the new AVRCP Target Service"); 116 sInstance = null; 117 return; 118 } 119 120 init(); 121 122 // Only allow the service to be used once it is initialized 123 sInstance = this; 124 } 125 126 @Override 127 protected boolean start() { 128 Log.i(TAG, "Starting the AVRCP Target Service"); 129 mCurrentData = new MediaData(null, null, null); 130 131 mReceiver = new AvrcpBroadcastReceiver(); 132 IntentFilter filter = new IntentFilter(); 133 filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 134 registerReceiver(mReceiver, filter); 135 136 return true; 137 } 138 139 @Override 140 protected boolean stop() { 141 Log.i(TAG, "Stopping the AVRCP Target Service"); 142 143 sInstance = null; 144 unregisterReceiver(mReceiver); 145 146 // We check the interfaces first since they only get set on User Unlocked 147 if (mMediaPlayerList != null) mMediaPlayerList.cleanup(); 148 if (mNativeInterface != null) mNativeInterface.cleanup(); 149 150 mMediaPlayerList = null; 151 mNativeInterface = null; 152 mAudioManager = null; 153 mReceiver = null; 154 return true; 155 } 156 157 private void init() { 158 if (mMediaPlayerList != null) { 159 Log.wtfStack(TAG, "init: The service has already been initialized"); 160 return; 161 } 162 163 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 164 sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 165 166 mMediaPlayerList = new MediaPlayerList(); 167 mMediaPlayerList.init(Looper.myLooper(), this, new ListCallback()); 168 mNativeInterface = AvrcpNativeInterface.getInterface(); 169 mNativeInterface.init(AvrcpTargetService.this); 170 } 171 172 void deviceConnected(String bdaddr, boolean absoluteVolume) { 173 Log.i(TAG, "deviceConnected: bdaddr=" + bdaddr + " absoluteVolume=" + absoluteVolume); 174 mAudioManager.avrcpSupportsAbsoluteVolume(bdaddr, absoluteVolume); 175 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP); 176 } 177 178 void deviceDisconnected(String bdaddr) { 179 // Do nothing 180 } 181 182 // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly. 183 void setVolume(int avrcpVolume) { 184 int deviceVolume = 185 (int) Math.floor((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL); 186 if (DEBUG) { 187 Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume 188 + " deviceVolume=" + deviceVolume 189 + " sDeviceMaxVolume=" + sDeviceMaxVolume); 190 } 191 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, deviceVolume, 192 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); 193 } 194 195 /** 196 * Set the volume on the remote device. Does nothing if the device doesn't support absolute 197 * volume. 198 */ 199 public void sendVolumeChanged(int deviceVolume) { 200 int avrcpVolume = 201 (int) Math.floor((double) deviceVolume * AVRCP_MAX_VOL / sDeviceMaxVolume); 202 if (avrcpVolume > 127) avrcpVolume = 127; 203 if (DEBUG) { 204 Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume 205 + " deviceVolume=" + deviceVolume 206 + " sDeviceMaxVolume=" + sDeviceMaxVolume); 207 } 208 mNativeInterface.sendVolumeChanged(avrcpVolume); 209 } 210 211 Metadata getCurrentSongInfo() { 212 return mMediaPlayerList.getCurrentSongInfo(); 213 } 214 215 PlayStatus getPlayState() { 216 return PlayStatus.fromPlaybackState(mMediaPlayerList.getCurrentPlayStatus(), 217 Long.parseLong(getCurrentSongInfo().duration)); 218 } 219 220 String getCurrentMediaId() { 221 String id = mMediaPlayerList.getCurrentMediaId(); 222 if (id != null) return id; 223 224 Metadata song = getCurrentSongInfo(); 225 if (song != null) return song.mediaId; 226 227 // We always want to return something, the error string just makes debugging easier 228 return "error"; 229 } 230 231 List<Metadata> getNowPlayingList() { 232 return mMediaPlayerList.getNowPlayingList(); 233 } 234 235 int getCurrentPlayerId() { 236 return mMediaPlayerList.getCurrentPlayerId(); 237 } 238 239 // TODO (apanicke): Have the Player List also contain info about the play state of each player 240 List<PlayerInfo> getMediaPlayerList() { 241 return mMediaPlayerList.getMediaPlayerList(); 242 } 243 244 void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) { 245 mMediaPlayerList.getPlayerRoot(playerId, cb); 246 } 247 248 void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) { 249 mMediaPlayerList.getFolderItems(playerId, mediaId, cb); 250 } 251 252 void playItem(int playerId, boolean nowPlaying, String mediaId) { 253 // NOTE: playerId isn't used if nowPlaying is true, since its assumed to be the current 254 // active player 255 mMediaPlayerList.playItem(playerId, nowPlaying, mediaId); 256 } 257 258 // TODO (apanicke): Handle key events here in the service. Currently it was more convenient to 259 // handle them there but logically they make more sense handled here. 260 void sendMediaKeyEvent(int event, int state) { 261 if (DEBUG) Log.d(TAG, "getMediaKeyEvent: event=" + event + " state=" + state); 262 mMediaPlayerList.sendMediaKeyEvent(event, state); 263 } 264 265 void setActiveDevice(String address) { 266 Log.i(TAG, "setActiveDevice: address=" + address); 267 BluetoothDevice d = 268 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address.toUpperCase()); 269 if (d == null) { 270 Log.wtfStack(TAG, "setActiveDevice: could not find device with address " + address); 271 } 272 A2dpService.getA2dpService().setActiveDevice(d); 273 } 274 275 /** 276 * Dump debugging information to the string builder 277 */ 278 public void dump(StringBuilder sb) { 279 if (mMediaPlayerList != null) { 280 mMediaPlayerList.dump(sb); 281 } else { 282 sb.append("\nMedia Player List is empty\n"); 283 } 284 } 285 286 private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub 287 implements IProfileServiceBinder { 288 private AvrcpTargetService mService; 289 290 AvrcpTargetBinder(AvrcpTargetService service) { 291 mService = service; 292 } 293 294 @Override 295 public void cleanup() { 296 mService = null; 297 } 298 299 @Override 300 public void sendVolumeChanged(int volume) { 301 if (!Utils.checkCaller()) { 302 Log.w(TAG, "sendVolumeChanged not allowed for non-active user"); 303 return; 304 } 305 306 if (mService == null) { 307 return; 308 } 309 310 mService.sendVolumeChanged(volume); 311 } 312 } 313} 314