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