• 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.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