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