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