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