• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.RequiresPermission;
20 import android.bluetooth.BluetoothA2dp;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothProfile;
23 import android.bluetooth.IBluetoothAvrcpTarget;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.media.AudioManager;
29 import android.os.Looper;
30 import android.os.SystemProperties;
31 import android.os.UserManager;
32 import android.util.Log;
33 
34 import com.android.bluetooth.BluetoothMetricsProto;
35 import com.android.bluetooth.R;
36 import com.android.bluetooth.Utils;
37 import com.android.bluetooth.a2dp.A2dpService;
38 import com.android.bluetooth.audio_util.BTAudioEventLogger;
39 import com.android.bluetooth.audio_util.MediaData;
40 import com.android.bluetooth.audio_util.MediaPlayerList;
41 import com.android.bluetooth.audio_util.MediaPlayerWrapper;
42 import com.android.bluetooth.audio_util.Metadata;
43 import com.android.bluetooth.audio_util.PlayStatus;
44 import com.android.bluetooth.audio_util.PlayerInfo;
45 import com.android.bluetooth.btservice.MetricsLogger;
46 import com.android.bluetooth.btservice.ProfileService;
47 import com.android.bluetooth.btservice.ServiceFactory;
48 import com.android.internal.annotations.VisibleForTesting;
49 
50 import java.util.List;
51 import java.util.Objects;
52 
53 /**
54  * Provides Bluetooth AVRCP Target profile as a service in the Bluetooth application.
55  * @hide
56  */
57 public class AvrcpTargetService extends ProfileService {
58     private static final String TAG = "AvrcpTargetService";
59     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
60     private static final String AVRCP_ENABLE_PROPERTY = "persist.bluetooth.enablenewavrcp";
61 
62     private static final int AVRCP_MAX_VOL = 127;
63     private static final int MEDIA_KEY_EVENT_LOGGER_SIZE = 20;
64     private static final String MEDIA_KEY_EVENT_LOGGER_TITLE = "Media Key Events";
65     private static int sDeviceMaxVolume = 0;
66     private final BTAudioEventLogger mMediaKeyEventLogger = new BTAudioEventLogger(
67             MEDIA_KEY_EVENT_LOGGER_SIZE, MEDIA_KEY_EVENT_LOGGER_TITLE);
68 
69     private AvrcpVersion mAvrcpVersion;
70     private MediaPlayerList mMediaPlayerList;
71     private AudioManager mAudioManager;
72     private AvrcpBroadcastReceiver mReceiver;
73     private AvrcpNativeInterface mNativeInterface;
74     private AvrcpVolumeManager mVolumeManager;
75     private ServiceFactory mFactory = new ServiceFactory();
76 
77     // Only used to see if the metadata has changed from its previous value
78     private MediaData mCurrentData;
79 
80     // Cover Art Service (Storage + BIP Server)
81     private AvrcpCoverArtService mAvrcpCoverArtService = null;
82 
83     private static AvrcpTargetService sInstance = null;
84 
85     class ListCallback implements MediaPlayerList.MediaUpdateCallback {
86         @Override
run(MediaData data)87         public void run(MediaData data) {
88             if (mNativeInterface == null) return;
89 
90             boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata);
91             boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state);
92             boolean queue = !Objects.equals(mCurrentData.queue, data.queue);
93 
94             if (DEBUG) {
95                 Log.d(TAG, "onMediaUpdated: track_changed=" + metadata
96                         + " state=" + state + " queue=" + queue);
97             }
98             mCurrentData = data;
99 
100             mNativeInterface.sendMediaUpdate(metadata, state, queue);
101         }
102 
103         @Override
run(boolean availablePlayers, boolean addressedPlayers, boolean uids)104         public void run(boolean availablePlayers, boolean addressedPlayers,
105                 boolean uids) {
106             if (mNativeInterface == null) return;
107 
108             mNativeInterface.sendFolderUpdate(availablePlayers, addressedPlayers, uids);
109         }
110     }
111 
112     private class AvrcpBroadcastReceiver extends BroadcastReceiver {
113         @Override
onReceive(Context context, Intent intent)114         public void onReceive(Context context, Intent intent) {
115             String action = intent.getAction();
116             if (action.equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
117                 if (mNativeInterface == null) return;
118 
119                 // Update all the playback status info for each connected device
120                 mNativeInterface.sendMediaUpdate(false, true, false);
121             } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
122                 if (mNativeInterface == null) return;
123 
124                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
125                 if (device == null) return;
126 
127                 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
128                 if (state == BluetoothProfile.STATE_DISCONNECTED) {
129                     // If there is no connection, disconnectDevice() will do nothing
130                     if (mNativeInterface.disconnectDevice(device.getAddress())) {
131                         Log.d(TAG, "request to disconnect device " + device);
132                     }
133                 }
134             } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
135                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
136                 if (streamType == AudioManager.STREAM_MUSIC) {
137                     int volume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
138                     BluetoothDevice activeDevice = getA2dpActiveDevice();
139                     if (activeDevice != null
140                             && !mVolumeManager.getAbsoluteVolumeSupported(activeDevice)) {
141                         Log.d(TAG, "stream volume change to " + volume + " " + activeDevice);
142                         mVolumeManager.storeVolumeForDevice(activeDevice, volume);
143                     }
144                 }
145             }
146         }
147     }
148 
149     /**
150      * Set the AvrcpTargetService instance.
151      */
152     @VisibleForTesting
set(AvrcpTargetService instance)153     public static void set(AvrcpTargetService instance) {
154         sInstance = instance;
155     }
156 
157     /**
158      * Get the AvrcpTargetService instance. Returns null if the service hasn't been initialized.
159      */
get()160     public static AvrcpTargetService get() {
161         return sInstance;
162     }
163 
getCoverArtService()164     public AvrcpCoverArtService getCoverArtService() {
165         return mAvrcpCoverArtService;
166     }
167 
168     @Override
getName()169     public String getName() {
170         return TAG;
171     }
172 
173     @Override
initBinder()174     protected IProfileServiceBinder initBinder() {
175         return new AvrcpTargetBinder(this);
176     }
177 
178     @Override
setUserUnlocked(int userId)179     protected void setUserUnlocked(int userId) {
180         Log.i(TAG, "User unlocked, initializing the service");
181 
182         if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
183             Log.w(TAG, "Skipping initialization of the new AVRCP Target Player List");
184             sInstance = null;
185             return;
186         }
187 
188         if (mMediaPlayerList != null) {
189             mMediaPlayerList.init(new ListCallback());
190         }
191     }
192 
193     @Override
start()194     protected boolean start() {
195         if (sInstance != null) {
196             Log.wtf(TAG, "The service has already been initialized");
197             return false;
198         }
199 
200         Log.i(TAG, "Starting the AVRCP Target Service");
201         mCurrentData = new MediaData(null, null, null);
202 
203         if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
204             Log.w(TAG, "Skipping initialization of the new AVRCP Target Service");
205             sInstance = null;
206             return true;
207         }
208 
209         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
210         sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
211 
212         mMediaPlayerList = new MediaPlayerList(Looper.myLooper(), this);
213 
214         mNativeInterface = AvrcpNativeInterface.getInterface();
215         mNativeInterface.init(AvrcpTargetService.this);
216 
217         mAvrcpVersion = AvrcpVersion.getCurrentSystemPropertiesValue();
218 
219         mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);
220 
221         UserManager userManager = UserManager.get(getApplicationContext());
222         if (userManager.isUserUnlocked()) {
223             mMediaPlayerList.init(new ListCallback());
224         }
225 
226         if (getResources().getBoolean(R.bool.avrcp_target_enable_cover_art)) {
227             if (mAvrcpVersion.isAtleastVersion(AvrcpVersion.AVRCP_VERSION_1_6)) {
228                 mAvrcpCoverArtService = new AvrcpCoverArtService(this);
229                 boolean started = mAvrcpCoverArtService.start();
230                 if (!started) {
231                     Log.e(TAG, "Failed to start cover art service");
232                     mAvrcpCoverArtService = null;
233                 }
234             } else {
235                 Log.e(TAG, "Please use AVRCP version 1.6 to enable cover art");
236             }
237         }
238 
239         mReceiver = new AvrcpBroadcastReceiver();
240         IntentFilter filter = new IntentFilter();
241         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
242         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
243         filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
244         registerReceiver(mReceiver, filter);
245 
246         // Only allow the service to be used once it is initialized
247         sInstance = this;
248 
249         return true;
250     }
251 
252     @Override
stop()253     protected boolean stop() {
254         Log.i(TAG, "Stopping the AVRCP Target Service");
255 
256         if (sInstance == null) {
257             Log.w(TAG, "stop() called before start()");
258             return true;
259         }
260 
261         if (mAvrcpCoverArtService != null) {
262             mAvrcpCoverArtService.stop();
263         }
264         mAvrcpCoverArtService = null;
265 
266         sInstance = null;
267         unregisterReceiver(mReceiver);
268 
269         // We check the interfaces first since they only get set on User Unlocked
270         if (mMediaPlayerList != null) mMediaPlayerList.cleanup();
271         if (mNativeInterface != null) mNativeInterface.cleanup();
272 
273         mMediaPlayerList = null;
274         mNativeInterface = null;
275         mAudioManager = null;
276         mReceiver = null;
277         return true;
278     }
279 
init()280     private void init() {
281     }
282 
getA2dpActiveDevice()283     private BluetoothDevice getA2dpActiveDevice() {
284         A2dpService service = mFactory.getA2dpService();
285         if (service == null) {
286             return null;
287         }
288         return service.getActiveDevice();
289     }
290 
setA2dpActiveDevice(BluetoothDevice device)291     private void setA2dpActiveDevice(BluetoothDevice device) {
292         A2dpService service = A2dpService.getA2dpService();
293         if (service == null) {
294             Log.d(TAG, "setA2dpActiveDevice: A2dp service not found");
295             return;
296         }
297         service.setActiveDevice(device);
298     }
299 
deviceConnected(BluetoothDevice device, boolean absoluteVolume)300     void deviceConnected(BluetoothDevice device, boolean absoluteVolume) {
301         Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
302         mVolumeManager.deviceConnected(device, absoluteVolume);
303         MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP);
304     }
305 
deviceDisconnected(BluetoothDevice device)306     void deviceDisconnected(BluetoothDevice device) {
307         Log.i(TAG, "deviceDisconnected: device=" + device);
308         mVolumeManager.deviceDisconnected(device);
309     }
310 
311     /**
312      * Signal to the service that the current audio out device has changed and to inform
313      * the audio service whether the new device supports absolute volume. If it does, also
314      * set the absolute volume level on the remote device.
315      */
volumeDeviceSwitched(BluetoothDevice device)316     public void volumeDeviceSwitched(BluetoothDevice device) {
317         if (DEBUG) {
318             Log.d(TAG, "volumeDeviceSwitched: device=" + device);
319         }
320         mVolumeManager.volumeDeviceSwitched(device);
321     }
322 
323     /**
324      * Remove the stored volume for a device.
325      */
removeStoredVolumeForDevice(BluetoothDevice device)326     public void removeStoredVolumeForDevice(BluetoothDevice device) {
327         if (device == null) return;
328 
329         mVolumeManager.removeStoredVolumeForDevice(device);
330     }
331 
332     /**
333      * Retrieve the remembered volume for a device. Returns -1 if there is no volume for the
334      * device.
335      */
getRememberedVolumeForDevice(BluetoothDevice device)336     public int getRememberedVolumeForDevice(BluetoothDevice device) {
337         if (device == null) return -1;
338 
339         return mVolumeManager.getVolume(device, mVolumeManager.getNewDeviceVolume());
340     }
341 
342     // TODO (apanicke): Add checks to rejectlist Absolute Volume devices if they behave poorly.
setVolume(int avrcpVolume)343     void setVolume(int avrcpVolume) {
344         BluetoothDevice activeDevice = getA2dpActiveDevice();
345         if (activeDevice == null) {
346             Log.d(TAG, "setVolume: no active device");
347             return;
348         }
349 
350         mVolumeManager.setVolume(activeDevice, avrcpVolume);
351     }
352 
353     /**
354      * Set the volume on the remote device. Does nothing if the device doesn't support absolute
355      * volume.
356      */
sendVolumeChanged(int deviceVolume)357     public void sendVolumeChanged(int deviceVolume) {
358         BluetoothDevice activeDevice = getA2dpActiveDevice();
359         if (activeDevice == null) {
360             Log.d(TAG, "sendVolumeChanged: no active device");
361             return;
362         }
363 
364         mVolumeManager.sendVolumeChanged(activeDevice, deviceVolume);
365     }
366 
getCurrentSongInfo()367     Metadata getCurrentSongInfo() {
368         Metadata metadata = mMediaPlayerList.getCurrentSongInfo();
369         if (mAvrcpCoverArtService != null && metadata.image != null) {
370             String imageHandle = mAvrcpCoverArtService.storeImage(metadata.image);
371             if (imageHandle != null) metadata.image.setImageHandle(imageHandle);
372         }
373         return metadata;
374     }
375 
getPlayState()376     PlayStatus getPlayState() {
377         return PlayStatus.fromPlaybackState(mMediaPlayerList.getCurrentPlayStatus(),
378                 Long.parseLong(getCurrentSongInfo().duration));
379     }
380 
getCurrentMediaId()381     String getCurrentMediaId() {
382         String id = mMediaPlayerList.getCurrentMediaId();
383         if (id != null) return id;
384 
385         Metadata song = getCurrentSongInfo();
386         if (song != null) return song.mediaId;
387 
388         // We always want to return something, the error string just makes debugging easier
389         return "error";
390     }
391 
getNowPlayingList()392     List<Metadata> getNowPlayingList() {
393         String currentMediaId = getCurrentMediaId();
394         Metadata currentTrack = null;
395         String imageHandle = null;
396         List<Metadata> nowPlayingList = mMediaPlayerList.getNowPlayingList();
397         if (mAvrcpCoverArtService != null) {
398             for (Metadata metadata : nowPlayingList) {
399                 if (metadata.mediaId == currentMediaId) {
400                     currentTrack = metadata;
401                 } else if (metadata.image != null) {
402                     imageHandle = mAvrcpCoverArtService.storeImage(metadata.image);
403                     if (imageHandle != null) {
404                         metadata.image.setImageHandle(imageHandle);
405                     }
406                 }
407             }
408 
409             // Always store the current item from the queue last so we know the image is in storage
410             if (currentTrack != null) {
411                 imageHandle = mAvrcpCoverArtService.storeImage(currentTrack.image);
412                 if (imageHandle != null) {
413                     currentTrack.image.setImageHandle(imageHandle);
414                 }
415             }
416         }
417         return nowPlayingList;
418     }
419 
getCurrentPlayerId()420     int getCurrentPlayerId() {
421         return mMediaPlayerList.getCurrentPlayerId();
422     }
423 
424     // TODO (apanicke): Have the Player List also contain info about the play state of each player
getMediaPlayerList()425     List<PlayerInfo> getMediaPlayerList() {
426         return mMediaPlayerList.getMediaPlayerList();
427     }
428 
getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb)429     void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) {
430         mMediaPlayerList.getPlayerRoot(playerId, cb);
431     }
432 
getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb)433     void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) {
434         mMediaPlayerList.getFolderItems(playerId, mediaId, cb);
435     }
436 
playItem(int playerId, boolean nowPlaying, String mediaId)437     void playItem(int playerId, boolean nowPlaying, String mediaId) {
438         // NOTE: playerId isn't used if nowPlaying is true, since its assumed to be the current
439         // active player
440         mMediaPlayerList.playItem(playerId, nowPlaying, mediaId);
441     }
442 
443     // TODO (apanicke): Handle key events here in the service. Currently it was more convenient to
444     // handle them there but logically they make more sense handled here.
sendMediaKeyEvent(int event, boolean pushed)445     void sendMediaKeyEvent(int event, boolean pushed) {
446         BluetoothDevice activeDevice = getA2dpActiveDevice();
447         MediaPlayerWrapper player = mMediaPlayerList.getActivePlayer();
448         mMediaKeyEventLogger.logd(DEBUG, TAG, "getMediaKeyEvent:" + " device=" + activeDevice
449                 + " event=" + event + " pushed=" + pushed
450                 + " to " + (player == null ? null : player.getPackageName()));
451         mMediaPlayerList.sendMediaKeyEvent(event, pushed);
452     }
453 
setActiveDevice(BluetoothDevice device)454     void setActiveDevice(BluetoothDevice device) {
455         Log.i(TAG, "setActiveDevice: device=" + device);
456         if (device == null) {
457             Log.wtf(TAG, "setActiveDevice: could not find device " + device);
458         }
459         setA2dpActiveDevice(device);
460     }
461 
462     /**
463      * Dump debugging information to the string builder
464      */
dump(StringBuilder sb)465     public void dump(StringBuilder sb) {
466         sb.append("\nProfile: AvrcpTargetService:\n");
467         if (sInstance == null) {
468             sb.append("AvrcpTargetService not running");
469             return;
470         }
471 
472         StringBuilder tempBuilder = new StringBuilder();
473         tempBuilder.append("AVRCP version: " + mAvrcpVersion + "\n");
474 
475         if (mMediaPlayerList != null) {
476             mMediaPlayerList.dump(tempBuilder);
477         } else {
478             tempBuilder.append("\nMedia Player List is empty\n");
479         }
480 
481         mMediaKeyEventLogger.dump(tempBuilder);
482         tempBuilder.append("\n");
483         mVolumeManager.dump(tempBuilder);
484         if (mAvrcpCoverArtService != null) {
485             tempBuilder.append("\n");
486             mAvrcpCoverArtService.dump(tempBuilder);
487         }
488 
489         // Tab everything over by two spaces
490         sb.append(tempBuilder.toString().replaceAll("(?m)^", "  "));
491     }
492 
493     private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub
494             implements IProfileServiceBinder {
495         private AvrcpTargetService mService;
496 
AvrcpTargetBinder(AvrcpTargetService service)497         AvrcpTargetBinder(AvrcpTargetService service) {
498             mService = service;
499         }
500 
501         @Override
cleanup()502         public void cleanup() {
503             mService = null;
504         }
505 
506         @Override
sendVolumeChanged(int volume)507         public void sendVolumeChanged(int volume) {
508             if (!Utils.callerIsSystemOrActiveUser(TAG, "sendVolumeChanged")) {
509                 return;
510             }
511 
512             if (mService == null) {
513                 return;
514             }
515 
516             mService.sendVolumeChanged(volume);
517         }
518     }
519 }
520