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
17package android.media;
18
19import android.app.PendingIntent;
20import android.content.ComponentName;
21import android.os.Binder;
22import android.os.IBinder;
23import android.os.IBinder.DeathRecipient;
24import android.os.RemoteException;
25import android.util.Log;
26
27import 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 */
36class 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
91        public RccPlaybackState(int state, long positionMs, float speed) {
92            mState = state;
93            mPositionMs = positionMs;
94            mSpeed = speed;
95        }
96
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
104        public String toString() {
105            return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
106        }
107
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
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
156        RcClientDeathHandler(IBinder cb, PendingIntent pi) {
157            mCb = cb;
158            mMediaIntent = pi;
159        }
160
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
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
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
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
214    static protected void setMediaFocusControl(MediaFocusControl mfc) {
215        sController = mfc;
216    }
217
218    /** precondition: mediaIntent != null */
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
244    protected int getRccId() {
245        return mRccId;
246    }
247
248    protected IRemoteControlClient getRcc() {
249        return mRcClient;
250    }
251
252    protected ComponentName getMediaButtonReceiver() {
253        return mReceiverComponent;
254    }
255
256    protected PendingIntent getMediaButtonIntent() {
257        return mMediaIntent;
258    }
259
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
272    protected boolean isPlaybackActive() {
273        return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
274    }
275
276    //---------------------------------------------
277    // Modify the records stored in the instance
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
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
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    //---------------------------------------------
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)
339    public void destroy() {
340        unlinkToRcClientDeath();
341        if (mToken != null) {
342            mToken.unlinkToDeath(this, 0);
343            mToken = null;
344        }
345    }
346
347    @Override
348    public void binderDied() {
349        sController.unregisterMediaButtonIntentAsync(mMediaIntent);
350    }
351
352    @Override
353    protected void finalize() throws Throwable {
354        destroy(); // unlink exception handled inside method
355        super.finalize();
356    }
357}
358