• 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 
38 import java.util.List;
39 import java.util.Objects;
40 
41 /**
42  * Provides Bluetooth AVRCP Target profile as a service in the Bluetooth application.
43  * @hide
44  */
45 public 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
run(MediaData data)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
run(boolean availablePlayers, boolean addressedPlayers, boolean uids)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
onReceive(Context context, Intent intent)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      */
get()104     public static AvrcpTargetService get() {
105         return sInstance;
106     }
107 
108     @Override
getName()109     public String getName() {
110         return TAG;
111     }
112 
113     @Override
initBinder()114     protected IProfileServiceBinder initBinder() {
115         return new AvrcpTargetBinder(this);
116     }
117 
118     @Override
setUserUnlocked(int userId)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
start()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
stop()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 
init()198     private void init() {
199     }
200 
deviceConnected(BluetoothDevice device, boolean absoluteVolume)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 
deviceDisconnected(BluetoothDevice device)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      */
volumeDeviceSwitched(BluetoothDevice device)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      */
storeVolumeForDevice(BluetoothDevice device)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      */
getRememberedVolumeForDevice(BluetoothDevice device)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.
setVolume(int avrcpVolume)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      */
sendVolumeChanged(int deviceVolume)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 
getCurrentSongInfo()272     Metadata getCurrentSongInfo() {
273         return mMediaPlayerList.getCurrentSongInfo();
274     }
275 
getPlayState()276     PlayStatus getPlayState() {
277         return PlayStatus.fromPlaybackState(mMediaPlayerList.getCurrentPlayStatus(),
278                 Long.parseLong(getCurrentSongInfo().duration));
279     }
280 
getCurrentMediaId()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 
getNowPlayingList()292     List<Metadata> getNowPlayingList() {
293         return mMediaPlayerList.getNowPlayingList();
294     }
295 
getCurrentPlayerId()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
getMediaPlayerList()301     List<PlayerInfo> getMediaPlayerList() {
302         return mMediaPlayerList.getMediaPlayerList();
303     }
304 
getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb)305     void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) {
306         mMediaPlayerList.getPlayerRoot(playerId, cb);
307     }
308 
getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb)309     void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) {
310         mMediaPlayerList.getFolderItems(playerId, mediaId, cb);
311     }
312 
playItem(int playerId, boolean nowPlaying, String mediaId)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.
sendMediaKeyEvent(int event, boolean pushed)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 
setActiveDevice(BluetoothDevice device)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      */
dump(StringBuilder sb)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 
AvrcpTargetBinder(AvrcpTargetService service)361         AvrcpTargetBinder(AvrcpTargetService service) {
362             mService = service;
363         }
364 
365         @Override
cleanup()366         public void cleanup() {
367             mService = null;
368         }
369 
370         @Override
sendVolumeChanged(int volume)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