AvrcpTargetService.java revision 0f5b951d00b5efb447774eabb899872e6cf83ccb
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.BluetoothDevice;
21import android.bluetooth.IBluetoothAvrcpTarget;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.media.AudioManager;
27import android.os.Looper;
28import android.os.SystemProperties;
29import android.os.UserManager;
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    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
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
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
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     */
104    public static AvrcpTargetService get() {
105        return sInstance;
106    }
107
108    @Override
109    public String getName() {
110        return TAG;
111    }
112
113    @Override
114    protected IProfileServiceBinder initBinder() {
115        return new AvrcpTargetBinder(this);
116    }
117
118    @Override
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
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
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
198    private void init() {
199    }
200
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
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     */
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     */
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     */
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.
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     */
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
272    Metadata getCurrentSongInfo() {
273        return mMediaPlayerList.getCurrentSongInfo();
274    }
275
276    PlayStatus getPlayState() {
277        return PlayStatus.fromPlaybackState(mMediaPlayerList.getCurrentPlayStatus(),
278                Long.parseLong(getCurrentSongInfo().duration));
279    }
280
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
292    List<Metadata> getNowPlayingList() {
293        return mMediaPlayerList.getNowPlayingList();
294    }
295
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
301    List<PlayerInfo> getMediaPlayerList() {
302        return mMediaPlayerList.getMediaPlayerList();
303    }
304
305    void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) {
306        mMediaPlayerList.getPlayerRoot(playerId, cb);
307    }
308
309    void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) {
310        mMediaPlayerList.getFolderItems(playerId, mediaId, cb);
311    }
312
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.
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
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     */
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
361        AvrcpTargetBinder(AvrcpTargetService service) {
362            mService = service;
363        }
364
365        @Override
366        public void cleanup() {
367            mService = null;
368        }
369
370        @Override
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