1 /* 2 * Copyright (C) 2014 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 android.media; 18 19 import android.app.PendingIntent; 20 import android.content.ComponentName; 21 import android.os.Binder; 22 import android.os.IBinder; 23 import android.os.IBinder.DeathRecipient; 24 import android.os.RemoteException; 25 import android.util.Log; 26 27 import java.io.PrintWriter; 28 29 /** 30 * @hide 31 * Class to handle all the information about a media player, encapsulating information 32 * about its use RemoteControlClient, playback type and volume... The lifecycle of each 33 * instance is managed by android.media.MediaFocusControl, from its addition to the player stack 34 * stack to its release. 35 */ 36 class PlayerRecord implements DeathRecipient { 37 38 // on purpose not using this classe's name, as it will only be used from MediaFocusControl 39 private static final String TAG = "MediaFocusControl"; 40 private static final boolean DEBUG = false; 41 42 /** 43 * A global counter for RemoteControlClient identifiers 44 */ 45 private static int sLastRccId = 0; 46 47 public static MediaFocusControl sController; 48 49 /** 50 * The target for the ACTION_MEDIA_BUTTON events. 51 * Always non null. //FIXME verify 52 */ 53 final private PendingIntent mMediaIntent; 54 /** 55 * The registered media button event receiver. 56 */ 57 final private ComponentName mReceiverComponent; 58 59 private int mRccId = -1; 60 61 /** 62 * A non-null token implies this record tracks a "live" player whose death is being monitored. 63 */ 64 private IBinder mToken; 65 private String mCallingPackageName; 66 private int mCallingUid; 67 /** 68 * Provides access to the information to display on the remote control. 69 * May be null (when a media button event receiver is registered, 70 * but no remote control client has been registered) */ 71 private IRemoteControlClient mRcClient; 72 private RcClientDeathHandler mRcClientDeathHandler; 73 /** 74 * Information only used for non-local playback 75 */ 76 //FIXME private? 77 public int mPlaybackType; 78 public int mPlaybackVolume; 79 public int mPlaybackVolumeMax; 80 public int mPlaybackVolumeHandling; 81 public int mPlaybackStream; 82 public RccPlaybackState mPlaybackState; 83 public IRemoteVolumeObserver mRemoteVolumeObs; 84 85 86 protected static class RccPlaybackState { 87 public int mState; 88 public long mPositionMs; 89 public float mSpeed; 90 RccPlaybackState(int state, long positionMs, float speed)91 public RccPlaybackState(int state, long positionMs, float speed) { 92 mState = state; 93 mPositionMs = positionMs; 94 mSpeed = speed; 95 } 96 reset()97 public void reset() { 98 mState = RemoteControlClient.PLAYSTATE_STOPPED; 99 mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; 100 mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; 101 } 102 103 @Override toString()104 public String toString() { 105 return stateToString() + ", " + posToString() + ", " + mSpeed + "X"; 106 } 107 posToString()108 private String posToString() { 109 if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) { 110 return "PLAYBACK_POSITION_INVALID"; 111 } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { 112 return "PLAYBACK_POSITION_ALWAYS_UNKNOWN"; 113 } else { 114 return (String.valueOf(mPositionMs) + "ms"); 115 } 116 } 117 stateToString()118 private String stateToString() { 119 switch (mState) { 120 case RemoteControlClient.PLAYSTATE_NONE: 121 return "PLAYSTATE_NONE"; 122 case RemoteControlClient.PLAYSTATE_STOPPED: 123 return "PLAYSTATE_STOPPED"; 124 case RemoteControlClient.PLAYSTATE_PAUSED: 125 return "PLAYSTATE_PAUSED"; 126 case RemoteControlClient.PLAYSTATE_PLAYING: 127 return "PLAYSTATE_PLAYING"; 128 case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: 129 return "PLAYSTATE_FAST_FORWARDING"; 130 case RemoteControlClient.PLAYSTATE_REWINDING: 131 return "PLAYSTATE_REWINDING"; 132 case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: 133 return "PLAYSTATE_SKIPPING_FORWARDS"; 134 case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: 135 return "PLAYSTATE_SKIPPING_BACKWARDS"; 136 case RemoteControlClient.PLAYSTATE_BUFFERING: 137 return "PLAYSTATE_BUFFERING"; 138 case RemoteControlClient.PLAYSTATE_ERROR: 139 return "PLAYSTATE_ERROR"; 140 default: 141 return "[invalid playstate]"; 142 } 143 } 144 } 145 146 147 /** 148 * Inner class to monitor remote control client deaths, and remove the client for the 149 * remote control stack if necessary. 150 */ 151 private class RcClientDeathHandler implements IBinder.DeathRecipient { 152 final private IBinder mCb; // To be notified of client's death 153 //FIXME needed? 154 final private PendingIntent mMediaIntent; 155 RcClientDeathHandler(IBinder cb, PendingIntent pi)156 RcClientDeathHandler(IBinder cb, PendingIntent pi) { 157 mCb = cb; 158 mMediaIntent = pi; 159 } 160 binderDied()161 public void binderDied() { 162 Log.w(TAG, " RemoteControlClient died"); 163 // remote control client died, make sure the displays don't use it anymore 164 // by setting its remote control client to null 165 sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/); 166 // the dead client was maybe handling remote playback, the controller should reevaluate 167 sController.postReevaluateRemote(); 168 } 169 getBinder()170 public IBinder getBinder() { 171 return mCb; 172 } 173 } 174 175 176 protected static class RemotePlaybackState { 177 int mRccId; 178 int mVolume; 179 int mVolumeMax; 180 int mVolumeHandling; 181 RemotePlaybackState(int id, int vol, int volMax)182 protected RemotePlaybackState(int id, int vol, int volMax) { 183 mRccId = id; 184 mVolume = vol; 185 mVolumeMax = volMax; 186 mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; 187 } 188 } 189 190 dump(PrintWriter pw, boolean registrationInfo)191 void dump(PrintWriter pw, boolean registrationInfo) { 192 if (registrationInfo) { 193 pw.println(" pi: " + mMediaIntent + 194 " -- pack: " + mCallingPackageName + 195 " -- ercvr: " + mReceiverComponent + 196 " -- client: " + mRcClient + 197 " -- uid: " + mCallingUid + 198 " -- type: " + mPlaybackType + 199 " state: " + mPlaybackState); 200 } else { 201 // emphasis on state 202 pw.println(" uid: " + mCallingUid + 203 " -- id: " + mRccId + 204 " -- type: " + mPlaybackType + 205 " -- state: " + mPlaybackState + 206 " -- vol handling: " + mPlaybackVolumeHandling + 207 " -- vol: " + mPlaybackVolume + 208 " -- volMax: " + mPlaybackVolumeMax + 209 " -- volObs: " + mRemoteVolumeObs); 210 } 211 } 212 213 setMediaFocusControl(MediaFocusControl mfc)214 static protected void setMediaFocusControl(MediaFocusControl mfc) { 215 sController = mfc; 216 } 217 218 /** precondition: mediaIntent != null */ PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)219 protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token) 220 { 221 mMediaIntent = mediaIntent; 222 mReceiverComponent = eventReceiver; 223 mToken = token; 224 mCallingUid = -1; 225 mRcClient = null; 226 mRccId = ++sLastRccId; 227 mPlaybackState = new RccPlaybackState( 228 RemoteControlClient.PLAYSTATE_STOPPED, 229 RemoteControlClient.PLAYBACK_POSITION_INVALID, 230 RemoteControlClient.PLAYBACK_SPEED_1X); 231 232 resetPlaybackInfo(); 233 if (mToken != null) { 234 try { 235 mToken.linkToDeath(this, 0); 236 } catch (RemoteException e) { 237 sController.unregisterMediaButtonIntentAsync(mMediaIntent); 238 } 239 } 240 } 241 242 //--------------------------------------------- 243 // Accessors getRccId()244 protected int getRccId() { 245 return mRccId; 246 } 247 getRcc()248 protected IRemoteControlClient getRcc() { 249 return mRcClient; 250 } 251 getMediaButtonReceiver()252 protected ComponentName getMediaButtonReceiver() { 253 return mReceiverComponent; 254 } 255 getMediaButtonIntent()256 protected PendingIntent getMediaButtonIntent() { 257 return mMediaIntent; 258 } 259 hasMatchingMediaButtonIntent(PendingIntent pi)260 protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) { 261 if (mToken != null) { 262 return mMediaIntent.equals(pi); 263 } else { 264 if (mReceiverComponent != null) { 265 return mReceiverComponent.equals(pi.getIntent().getComponent()); 266 } else { 267 return false; 268 } 269 } 270 } 271 isPlaybackActive()272 protected boolean isPlaybackActive() { 273 return MediaFocusControl.isPlaystateActive(mPlaybackState.mState); 274 } 275 276 //--------------------------------------------- 277 // Modify the records stored in the instance resetControllerInfoForRcc(IRemoteControlClient rcClient, String callingPackageName, int uid)278 protected void resetControllerInfoForRcc(IRemoteControlClient rcClient, 279 String callingPackageName, int uid) { 280 // already had a remote control client? 281 if (mRcClientDeathHandler != null) { 282 // stop monitoring the old client's death 283 unlinkToRcClientDeath(); 284 } 285 // save the new remote control client 286 mRcClient = rcClient; 287 mCallingPackageName = callingPackageName; 288 mCallingUid = uid; 289 if (rcClient == null) { 290 // here mcse.mRcClientDeathHandler is null; 291 resetPlaybackInfo(); 292 } else { 293 IBinder b = mRcClient.asBinder(); 294 RcClientDeathHandler rcdh = 295 new RcClientDeathHandler(b, mMediaIntent); 296 try { 297 b.linkToDeath(rcdh, 0); 298 } catch (RemoteException e) { 299 // remote control client is DOA, disqualify it 300 Log.w(TAG, "registerRemoteControlClient() has a dead client " + b); 301 mRcClient = null; 302 } 303 mRcClientDeathHandler = rcdh; 304 } 305 } 306 resetControllerInfoForNoRcc()307 protected void resetControllerInfoForNoRcc() { 308 // stop monitoring the RCC death 309 unlinkToRcClientDeath(); 310 // reset the RCC-related fields 311 mRcClient = null; 312 mCallingPackageName = null; 313 } 314 resetPlaybackInfo()315 public void resetPlaybackInfo() { 316 mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL; 317 mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; 318 mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; 319 mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; 320 mPlaybackStream = AudioManager.STREAM_MUSIC; 321 mPlaybackState.reset(); 322 mRemoteVolumeObs = null; 323 } 324 325 //--------------------------------------------- unlinkToRcClientDeath()326 public void unlinkToRcClientDeath() { 327 if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) { 328 try { 329 mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0); 330 mRcClientDeathHandler = null; 331 } catch (java.util.NoSuchElementException e) { 332 // not much we can do here 333 Log.e(TAG, "Error in unlinkToRcClientDeath()", e); 334 } 335 } 336 } 337 338 // FIXME rename to "release"? (as in FocusRequester class) destroy()339 public void destroy() { 340 unlinkToRcClientDeath(); 341 if (mToken != null) { 342 mToken.unlinkToDeath(this, 0); 343 mToken = null; 344 } 345 } 346 347 @Override binderDied()348 public void binderDied() { 349 sController.unregisterMediaButtonIntentAsync(mMediaIntent); 350 } 351 352 @Override finalize()353 protected void finalize() throws Throwable { 354 destroy(); // unlink exception handled inside method 355 super.finalize(); 356 } 357 } 358