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