1 /*
2  * Copyright 2023 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.audio_util;
18 
19 import android.support.v4.media.session.MediaControllerCompat;
20 import android.support.v4.media.session.MediaSessionCompat;
21 import android.support.v4.media.session.PlaybackStateCompat;
22 import android.util.Log;
23 
24 import com.android.bluetooth.avrcp.AvrcpTargetService;
25 
26 /** Manager class for player apps. */
27 public class PlayerSettingsManager {
28     private static final String TAG = PlayerSettingsManager.class.getSimpleName();
29 
30     private final MediaPlayerList mMediaPlayerList;
31     private final AvrcpTargetService mService;
32 
33     private MediaControllerCompat mActivePlayerController;
34     private final MediaControllerCallback mControllerCallback;
35 
36     /**
37      * Instantiates a new PlayerSettingsManager.
38      *
39      * @param mediaPlayerList is used to retrieve the current active player.
40      */
PlayerSettingsManager(MediaPlayerList mediaPlayerList, AvrcpTargetService service)41     public PlayerSettingsManager(MediaPlayerList mediaPlayerList, AvrcpTargetService service) {
42         mService = service;
43         mMediaPlayerList = mediaPlayerList;
44         mMediaPlayerList.setPlayerSettingsCallback(
45                 (mediaPlayerWrapper) -> activePlayerChanged(mediaPlayerWrapper));
46         mControllerCallback = new MediaControllerCallback();
47 
48         MediaPlayerWrapper wrapper = mMediaPlayerList.getActivePlayer();
49         if (wrapper != null) {
50             mActivePlayerController =
51                     new MediaControllerCompat(
52                             mService,
53                             MediaSessionCompat.Token.fromToken(wrapper.getSessionToken()));
54             if (!registerMediaControllerCallback(mActivePlayerController, mControllerCallback)) {
55                 mActivePlayerController = null;
56             }
57         } else {
58             mActivePlayerController = null;
59         }
60     }
61 
62     /** Unregister callbacks */
cleanup()63     public void cleanup() {
64         updateRemoteDevice();
65         if (mActivePlayerController != null) {
66             unregisterMediaControllerCallback(mActivePlayerController, mControllerCallback);
67         }
68     }
69 
70     /** Updates the active player controller. */
activePlayerChanged(MediaPlayerWrapper mediaPlayerWrapper)71     private void activePlayerChanged(MediaPlayerWrapper mediaPlayerWrapper) {
72         if (mActivePlayerController != null) {
73             unregisterMediaControllerCallback(mActivePlayerController, mControllerCallback);
74         }
75         if (mediaPlayerWrapper != null) {
76             mActivePlayerController =
77                     new MediaControllerCompat(
78                             mService,
79                             MediaSessionCompat.Token.fromToken(
80                                     mediaPlayerWrapper.getSessionToken()));
81             if (!registerMediaControllerCallback(mActivePlayerController, mControllerCallback)) {
82                 mActivePlayerController = null;
83                 updateRemoteDevice();
84             }
85         } else {
86             mActivePlayerController = null;
87             updateRemoteDevice();
88         }
89     }
90 
91     /**
92      * Sends the MediaController values of the active player to the remote device.
93      *
94      * <p>This is called when: - The class is created and the session is ready - The class is
95      * destroyed - The active player changed and the session is ready - The last active player has
96      * been removed - The repeat / shuffle player state changed
97      */
updateRemoteDevice()98     private void updateRemoteDevice() {
99         int repeatMode = getPlayerRepeatMode();
100         int shuffleMode = getPlayerShuffleMode();
101         Log.i(
102                 TAG,
103                 "updateRemoteDevice: "
104                         + getRepeatModeStringValue(repeatMode)
105                         + ", "
106                         + getShuffleModeStringValue(shuffleMode));
107         mService.sendPlayerSettings(repeatMode, shuffleMode);
108     }
109 
110     /** Called from remote device to set the active player repeat mode. */
setPlayerRepeatMode(int repeatMode)111     public boolean setPlayerRepeatMode(int repeatMode) {
112         if (mActivePlayerController == null) {
113             return false;
114         }
115         MediaControllerCompat.TransportControls controls;
116         try {
117             controls = mActivePlayerController.getTransportControls();
118         } catch (Exception e) {
119             Log.e(TAG, e.toString());
120             return false;
121         }
122         switch (repeatMode) {
123             case PlayerSettingsValues.STATE_REPEAT_OFF:
124                 controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
125                 return true;
126             case PlayerSettingsValues.STATE_REPEAT_SINGLE_TRACK:
127                 controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ONE);
128                 return true;
129             case PlayerSettingsValues.STATE_REPEAT_GROUP:
130                 controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_GROUP);
131                 return true;
132             case PlayerSettingsValues.STATE_REPEAT_ALL_TRACK:
133                 controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ALL);
134                 return true;
135             default:
136                 controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
137                 return false;
138         }
139     }
140 
141     /** Called from remote device to set the active player shuffle mode. */
setPlayerShuffleMode(int shuffleMode)142     public boolean setPlayerShuffleMode(int shuffleMode) {
143         if (mActivePlayerController == null) {
144             Log.i(TAG, "setPlayerShuffleMode: no active player");
145             return false;
146         }
147         MediaControllerCompat.TransportControls controls;
148         try {
149             controls = mActivePlayerController.getTransportControls();
150         } catch (Exception e) {
151             Log.e(TAG, e.toString());
152             return false;
153         }
154         switch (shuffleMode) {
155             case PlayerSettingsValues.STATE_SHUFFLE_OFF:
156                 controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
157                 return true;
158             case PlayerSettingsValues.STATE_SHUFFLE_GROUP:
159                 controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_GROUP);
160                 return true;
161             case PlayerSettingsValues.STATE_SHUFFLE_ALL_TRACK:
162                 controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
163                 return true;
164             default:
165                 controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
166                 return false;
167         }
168     }
169 
170     /**
171      * Retrieves & converts the repeat value of the active player MediaController to AVRCP values
172      */
getPlayerRepeatMode()173     public int getPlayerRepeatMode() {
174         if (mActivePlayerController == null) {
175             Log.i(TAG, "getPlayerRepeatMode: no active player");
176             return PlayerSettingsValues.STATE_REPEAT_OFF;
177         }
178         int mediaFwkMode;
179         try {
180             mediaFwkMode = mActivePlayerController.getRepeatMode();
181         } catch (Exception e) {
182             Log.e(TAG, e.toString());
183             return PlayerSettingsValues.STATE_REPEAT_OFF;
184         }
185         switch (mediaFwkMode) {
186             case PlaybackStateCompat.REPEAT_MODE_NONE:
187                 return PlayerSettingsValues.STATE_REPEAT_OFF;
188             case PlaybackStateCompat.REPEAT_MODE_ONE:
189                 return PlayerSettingsValues.STATE_REPEAT_SINGLE_TRACK;
190             case PlaybackStateCompat.REPEAT_MODE_GROUP:
191                 return PlayerSettingsValues.STATE_REPEAT_GROUP;
192             case PlaybackStateCompat.REPEAT_MODE_ALL:
193                 return PlayerSettingsValues.STATE_REPEAT_ALL_TRACK;
194             case PlaybackStateCompat.REPEAT_MODE_INVALID:
195                 return PlayerSettingsValues.STATE_REPEAT_OFF;
196             default:
197                 return PlayerSettingsValues.STATE_REPEAT_OFF;
198         }
199     }
200 
201     /**
202      * Retrieves & converts the shuffle value of the active player MediaController to AVRCP values
203      */
getPlayerShuffleMode()204     public int getPlayerShuffleMode() {
205         if (mActivePlayerController == null) {
206             Log.i(TAG, "getPlayerShuffleMode: no active player");
207             return PlayerSettingsValues.STATE_SHUFFLE_OFF;
208         }
209         int mediaFwkMode;
210         try {
211             mediaFwkMode = mActivePlayerController.getShuffleMode();
212         } catch (Exception e) {
213             Log.e(TAG, e.toString());
214             return PlayerSettingsValues.STATE_SHUFFLE_OFF;
215         }
216         switch (mediaFwkMode) {
217             case PlaybackStateCompat.SHUFFLE_MODE_NONE:
218                 return PlayerSettingsValues.STATE_SHUFFLE_OFF;
219             case PlaybackStateCompat.SHUFFLE_MODE_GROUP:
220                 return PlayerSettingsValues.STATE_SHUFFLE_GROUP;
221             case PlaybackStateCompat.SHUFFLE_MODE_ALL:
222                 return PlayerSettingsValues.STATE_SHUFFLE_ALL_TRACK;
223             case PlaybackStateCompat.SHUFFLE_MODE_INVALID:
224                 return PlayerSettingsValues.STATE_SHUFFLE_OFF;
225             default:
226                 return PlayerSettingsValues.STATE_SHUFFLE_OFF;
227         }
228     }
229 
230     /**
231      * The binder of some MediaControllers can fail to register the callback, this result on a crash
232      * on the media side that has to be handled here.
233      */
registerMediaControllerCallback( MediaControllerCompat controller, MediaControllerCallback callback)234     private static boolean registerMediaControllerCallback(
235             MediaControllerCompat controller, MediaControllerCallback callback) {
236         try {
237             controller.registerCallback(callback);
238         } catch (Exception e) {
239             Log.e(TAG, e.toString());
240             return false;
241         }
242         return true;
243     }
244 
245     /**
246      * The binder of some MediaControllers can fail to unregister the callback, this result on a
247      * crash on the media side that has to be handled here.
248      */
unregisterMediaControllerCallback( MediaControllerCompat controller, MediaControllerCallback callback)249     private static boolean unregisterMediaControllerCallback(
250             MediaControllerCompat controller, MediaControllerCallback callback) {
251         try {
252             controller.unregisterCallback(callback);
253         } catch (Exception e) {
254             Log.e(TAG, e.toString());
255             return false;
256         }
257         return true;
258     }
259 
260     // Receives callbacks from the MediaControllerCompat.
261     private class MediaControllerCallback extends MediaControllerCompat.Callback {
262         @Override
onRepeatModeChanged(final int repeatMode)263         public void onRepeatModeChanged(final int repeatMode) {
264             updateRemoteDevice();
265         }
266 
267         @Override
onSessionReady()268         public void onSessionReady() {
269             updateRemoteDevice();
270         }
271 
272         @Override
onShuffleModeChanged(final int shuffleMode)273         public void onShuffleModeChanged(final int shuffleMode) {
274             updateRemoteDevice();
275         }
276     }
277 
278     /** Class containing all the Shuffle/Repeat values as defined in the BT spec. */
279     public static final class PlayerSettingsValues {
280         /** Repeat setting, as defined by Bluetooth specification. */
281         public static final int SETTING_REPEAT = 2;
282 
283         /** Shuffle setting, as defined by Bluetooth specification. */
284         public static final int SETTING_SHUFFLE = 3;
285 
286         /** Repeat OFF state, as defined by Bluetooth specification. */
287         public static final int STATE_REPEAT_OFF = 1;
288 
289         /** Single track repeat, as defined by Bluetooth specification. */
290         public static final int STATE_REPEAT_SINGLE_TRACK = 2;
291 
292         /** All track repeat, as defined by Bluetooth specification. */
293         public static final int STATE_REPEAT_ALL_TRACK = 3;
294 
295         /** Group repeat, as defined by Bluetooth specification. */
296         public static final int STATE_REPEAT_GROUP = 4;
297 
298         /** Shuffle OFF state, as defined by Bluetooth specification. */
299         public static final int STATE_SHUFFLE_OFF = 1;
300 
301         /** All track shuffle, as defined by Bluetooth specification. */
302         public static final int STATE_SHUFFLE_ALL_TRACK = 2;
303 
304         /** Group shuffle, as defined by Bluetooth specification. */
305         public static final int STATE_SHUFFLE_GROUP = 3;
306 
307         /** Default state off. */
308         public static final int STATE_DEFAULT_OFF = 1;
309     }
310 
getRepeatModeStringValue(int repeatMode)311     private static String getRepeatModeStringValue(int repeatMode) {
312         switch (repeatMode) {
313             case PlayerSettingsValues.STATE_REPEAT_OFF:
314                 return "STATE_REPEAT_OFF";
315             case PlayerSettingsValues.STATE_REPEAT_SINGLE_TRACK:
316                 return "STATE_REPEAT_SINGLE_TRACK";
317             case PlayerSettingsValues.STATE_REPEAT_ALL_TRACK:
318                 return "STATE_REPEAT_ALL_TRACK";
319             case PlayerSettingsValues.STATE_REPEAT_GROUP:
320                 return "STATE_REPEAT_GROUP";
321             default:
322                 return "STATE_DEFAULT_OFF";
323         }
324     }
325 
getShuffleModeStringValue(int shuffleMode)326     private static String getShuffleModeStringValue(int shuffleMode) {
327         switch (shuffleMode) {
328             case PlayerSettingsValues.STATE_SHUFFLE_OFF:
329                 return "STATE_SHUFFLE_OFF";
330             case PlayerSettingsValues.STATE_SHUFFLE_ALL_TRACK:
331                 return "STATE_SHUFFLE_ALL_TRACK";
332             case PlayerSettingsValues.STATE_SHUFFLE_GROUP:
333                 return "STATE_SHUFFLE_GROUP";
334             default:
335                 return "STATE_DEFAULT_OFF";
336         }
337     }
338 }
339