MediaFocusControl.java revision 86142da1ce8c1341404a9f9e21be8acbcba69ab3
1/*
2 * Copyright (C) 2013 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.Activity;
20import android.app.AppOpsManager;
21import android.app.KeyguardManager;
22import android.app.PendingIntent;
23import android.app.PendingIntent.CanceledException;
24import android.app.PendingIntent.OnFinished;
25import android.content.ActivityNotFoundException;
26import android.content.BroadcastReceiver;
27import android.content.ComponentName;
28import android.content.ContentResolver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.PackageManager;
33import android.os.Binder;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.Looper;
38import android.os.Message;
39import android.os.PowerManager;
40import android.os.RemoteException;
41import android.os.UserHandle;
42import android.os.IBinder.DeathRecipient;
43import android.provider.Settings;
44import android.speech.RecognizerIntent;
45import android.telephony.PhoneStateListener;
46import android.telephony.TelephonyManager;
47import android.util.Log;
48import android.view.KeyEvent;
49
50import java.io.FileDescriptor;
51import java.io.PrintWriter;
52import java.util.ArrayList;
53import java.util.Iterator;
54import java.util.Stack;
55
56/**
57 * @hide
58 *
59 */
60public class MediaFocusControl implements OnFinished {
61
62    private static final String TAG = "MediaFocusControl";
63
64    /** Debug remote control client/display feature */
65    protected static final boolean DEBUG_RC = false;
66    /** Debug volumes */
67    protected static final boolean DEBUG_VOL = false;
68
69    /** Used to alter media button redirection when the phone is ringing. */
70    private boolean mIsRinging = false;
71
72    private final PowerManager.WakeLock mMediaEventWakeLock;
73    private final MediaEventHandler mEventHandler;
74    private final Context mContext;
75    private final ContentResolver mContentResolver;
76    private final VolumeController mVolumeController;
77    private final BroadcastReceiver mReceiver = new PackageIntentsReceiver();
78    private final AppOpsManager mAppOps;
79    private final KeyguardManager mKeyguardManager;
80    private final AudioService mAudioService;
81
82    protected MediaFocusControl(Looper looper, Context cntxt,
83            VolumeController volumeCtrl, AudioService as) {
84        mEventHandler = new MediaEventHandler(looper);
85        mContext = cntxt;
86        mContentResolver = mContext.getContentResolver();
87        mVolumeController = volumeCtrl;
88        mAudioService = as;
89
90        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
91        mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
92        mMainRemote = new RemotePlaybackState(-1,
93                AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC),
94                AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC));
95
96        // Register for phone state monitoring
97        TelephonyManager tmgr = (TelephonyManager)
98                mContext.getSystemService(Context.TELEPHONY_SERVICE);
99        tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
100
101        // Register for package addition/removal/change intent broadcasts
102        //    for media button receiver persistence
103        IntentFilter pkgFilter = new IntentFilter();
104        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
105        pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
106        pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
107        pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
108        pkgFilter.addDataScheme("package");
109        mContext.registerReceiver(mReceiver, pkgFilter);
110
111        mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
112        mKeyguardManager =
113                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
114
115        mHasRemotePlayback = false;
116        mMainRemoteIsActive = false;
117        postReevaluateRemote();
118    }
119
120    protected void dump(PrintWriter pw) {
121        dumpFocusStack(pw);
122        dumpRCStack(pw);
123        dumpRCCStack(pw);
124        dumpRCDList(pw);
125    }
126
127    //==========================================================================================
128    // Internal event handling
129    //==========================================================================================
130
131    // event handler messages
132    private static final int MSG_PERSIST_MEDIABUTTONRECEIVER = 0;
133    private static final int MSG_RCDISPLAY_CLEAR = 1;
134    private static final int MSG_RCDISPLAY_UPDATE = 2;
135    private static final int MSG_REEVALUATE_REMOTE = 3;
136    private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4;
137    private static final int MSG_RCC_NEW_VOLUME_OBS = 5;
138    private static final int MSG_PROMOTE_RCC = 6;
139    private static final int MSG_RCC_NEW_PLAYBACK_STATE = 7;
140    private static final int MSG_RCC_SEEK_REQUEST = 8;
141    private static final int MSG_RCC_UPDATE_METADATA = 9;
142    private static final int MSG_RCDISPLAY_INIT_INFO = 10;
143
144    // sendMsg() flags
145    /** If the msg is already queued, replace it with this one. */
146    private static final int SENDMSG_REPLACE = 0;
147    /** If the msg is already queued, ignore this one and leave the old. */
148    private static final int SENDMSG_NOOP = 1;
149    /** If the msg is already queued, queue this one and leave the old. */
150    private static final int SENDMSG_QUEUE = 2;
151
152    private static void sendMsg(Handler handler, int msg,
153            int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
154
155        if (existingMsgPolicy == SENDMSG_REPLACE) {
156            handler.removeMessages(msg);
157        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
158            return;
159        }
160
161        handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
162    }
163
164    private class MediaEventHandler extends Handler {
165        MediaEventHandler(Looper looper) {
166            super(looper);
167        }
168
169        @Override
170        public void handleMessage(Message msg) {
171            switch(msg.what) {
172                case MSG_PERSIST_MEDIABUTTONRECEIVER:
173                    onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj );
174                    break;
175
176                case MSG_RCDISPLAY_CLEAR:
177                    onRcDisplayClear();
178                    break;
179
180                case MSG_RCDISPLAY_UPDATE:
181                    // msg.obj is guaranteed to be non null
182                    onRcDisplayUpdate( (RemoteControlStackEntry) msg.obj, msg.arg1);
183                    break;
184
185                case MSG_REEVALUATE_REMOTE:
186                    onReevaluateRemote();
187                    break;
188
189                case MSG_RCC_NEW_PLAYBACK_INFO:
190                    onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */,
191                            ((Integer)msg.obj).intValue() /* value */);
192                    break;
193
194                case MSG_RCC_NEW_VOLUME_OBS:
195                    onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
196                            (IRemoteVolumeObserver)msg.obj /* rvo */);
197                    break;
198
199                case MSG_RCC_NEW_PLAYBACK_STATE:
200                    onNewPlaybackStateForRcc(msg.arg1 /* rccId */,
201                            msg.arg2 /* state */,
202                            (RccPlaybackState)msg.obj /* newState */);
203                    break;
204
205                case MSG_RCC_SEEK_REQUEST:
206                    onSetRemoteControlClientPlaybackPosition(
207                            msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */);
208                    break;
209
210                case MSG_RCC_UPDATE_METADATA:
211                    onUpdateRemoteControlClientMetadata(msg.arg1 /*genId*/, msg.arg2 /*key*/,
212                            (Rating) msg.obj /* value */);
213                    break;
214
215                case MSG_PROMOTE_RCC:
216                    onPromoteRcc(msg.arg1);
217                    break;
218
219                case MSG_RCDISPLAY_INIT_INFO:
220                    // msg.obj is guaranteed to be non null
221                    onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
222                            msg.arg1/*w*/, msg.arg2/*h*/);
223                    break;
224            }
225        }
226    }
227
228
229    //==========================================================================================
230    // AudioFocus
231    //==========================================================================================
232
233    /* constant to identify focus stack entry that is used to hold the focus while the phone
234     * is ringing or during a call. Used by com.android.internal.telephony.CallManager when
235     * entering and exiting calls.
236     */
237    protected final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls";
238
239    private final static Object mAudioFocusLock = new Object();
240
241    private final static Object mRingingLock = new Object();
242
243    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
244        @Override
245        public void onCallStateChanged(int state, String incomingNumber) {
246            if (state == TelephonyManager.CALL_STATE_RINGING) {
247                //Log.v(TAG, " CALL_STATE_RINGING");
248                synchronized(mRingingLock) {
249                    mIsRinging = true;
250                }
251            } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK)
252                    || (state == TelephonyManager.CALL_STATE_IDLE)) {
253                synchronized(mRingingLock) {
254                    mIsRinging = false;
255                }
256            }
257        }
258    };
259
260    /**
261     * Discard the current audio focus owner.
262     * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
263     * focus), remove it from the stack, and clear the remote control display.
264     */
265    protected void discardAudioFocusOwner() {
266        synchronized(mAudioFocusLock) {
267            if (!mFocusStack.empty()) {
268                // notify the current focus owner it lost focus after removing it from stack
269                final FocusRequester exFocusOwner = mFocusStack.pop();
270                exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
271                exFocusOwner.release();
272                // clear RCD
273                synchronized(mRCStack) {
274                    clearRemoteControlDisplay_syncAfRcs();
275                }
276            }
277        }
278    }
279
280    private void notifyTopOfAudioFocusStack() {
281        // notify the top of the stack it gained focus
282        if (!mFocusStack.empty()) {
283            if (canReassignAudioFocus()) {
284                mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
285            }
286        }
287    }
288
289    /**
290     * Focus is requested, propagate the associated loss throughout the stack.
291     * @param focusGain the new focus gain that will later be added at the top of the stack
292     */
293    private void propagateFocusLossFromGain_syncAf(int focusGain) {
294        // going through the audio focus stack to signal new focus, traversing order doesn't
295        // matter as all entries respond to the same external focus gain
296        Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
297        while(stackIterator.hasNext()) {
298            stackIterator.next().handleExternalFocusGain(focusGain);
299        }
300    }
301
302    private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
303
304    /**
305     * Helper function:
306     * Display in the log the current entries in the audio focus stack
307     */
308    private void dumpFocusStack(PrintWriter pw) {
309        pw.println("\nAudio Focus stack entries (last is top of stack):");
310        synchronized(mAudioFocusLock) {
311            Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
312            while(stackIterator.hasNext()) {
313                stackIterator.next().dump(pw);
314            }
315        }
316    }
317
318    /**
319     * Helper function:
320     * Called synchronized on mAudioFocusLock
321     * Remove a focus listener from the focus stack.
322     * @param clientToRemove the focus listener
323     * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
324     *   focus, notify the next item in the stack it gained focus.
325     */
326    private void removeFocusStackEntry(String clientToRemove, boolean signal) {
327        // is the current top of the focus stack abandoning focus? (because of request, not death)
328        if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
329        {
330            //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
331            FocusRequester fr = mFocusStack.pop();
332            fr.release();
333            if (signal) {
334                // notify the new top of the stack it gained focus
335                notifyTopOfAudioFocusStack();
336                // there's a new top of the stack, let the remote control know
337                synchronized(mRCStack) {
338                    checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
339                }
340            }
341        } else {
342            // focus is abandoned by a client that's not at the top of the stack,
343            // no need to update focus.
344            // (using an iterator on the stack so we can safely remove an entry after having
345            //  evaluated it, traversal order doesn't matter here)
346            Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
347            while(stackIterator.hasNext()) {
348                FocusRequester fr = (FocusRequester)stackIterator.next();
349                if(fr.hasSameClient(clientToRemove)) {
350                    Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
351                            + clientToRemove);
352                    stackIterator.remove();
353                    fr.release();
354                }
355            }
356        }
357    }
358
359    /**
360     * Helper function:
361     * Called synchronized on mAudioFocusLock
362     * Remove focus listeners from the focus stack for a particular client when it has died.
363     */
364    private void removeFocusStackEntryForClient(IBinder cb) {
365        // is the owner of the audio focus part of the client to remove?
366        boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
367                mFocusStack.peek().hasSameBinder(cb);
368        // (using an iterator on the stack so we can safely remove an entry after having
369        //  evaluated it, traversal order doesn't matter here)
370        Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
371        while(stackIterator.hasNext()) {
372            FocusRequester fr = (FocusRequester)stackIterator.next();
373            if(fr.hasSameBinder(cb)) {
374                Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for " + cb);
375                stackIterator.remove();
376                // the client just died, no need to unlink to its death
377            }
378        }
379        if (isTopOfStackForClientToRemove) {
380            // we removed an entry at the top of the stack:
381            //  notify the new top of the stack it gained focus.
382            notifyTopOfAudioFocusStack();
383            // there's a new top of the stack, let the remote control know
384            synchronized(mRCStack) {
385                checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
386            }
387        }
388    }
389
390    /**
391     * Helper function:
392     * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
393     */
394    private boolean canReassignAudioFocus() {
395        // focus requests are rejected during a phone call or when the phone is ringing
396        // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
397        if (!mFocusStack.isEmpty() && mFocusStack.peek().hasSameClient(IN_VOICE_COMM_FOCUS_ID)) {
398            return false;
399        }
400        return true;
401    }
402
403    /**
404     * Inner class to monitor audio focus client deaths, and remove them from the audio focus
405     * stack if necessary.
406     */
407    protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
408        private IBinder mCb; // To be notified of client's death
409
410        AudioFocusDeathHandler(IBinder cb) {
411            mCb = cb;
412        }
413
414        public void binderDied() {
415            synchronized(mAudioFocusLock) {
416                Log.w(TAG, "  AudioFocus   audio focus client died");
417                removeFocusStackEntryForClient(mCb);
418            }
419        }
420
421        public IBinder getBinder() {
422            return mCb;
423        }
424    }
425
426    protected int getCurrentAudioFocus() {
427        synchronized(mAudioFocusLock) {
428            if (mFocusStack.empty()) {
429                return AudioManager.AUDIOFOCUS_NONE;
430            } else {
431                return mFocusStack.peek().getGainRequest();
432            }
433        }
434    }
435
436    /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int)  */
437    protected int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb,
438            IAudioFocusDispatcher fd, String clientId, String callingPackageName) {
439        Log.i(TAG, " AudioFocus  requestAudioFocus() from " + clientId);
440        // we need a valid binder callback for clients
441        if (!cb.pingBinder()) {
442            Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
443            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
444        }
445
446        if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
447                callingPackageName) != AppOpsManager.MODE_ALLOWED) {
448            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
449        }
450
451        synchronized(mAudioFocusLock) {
452            if (!canReassignAudioFocus()) {
453                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
454            }
455
456            // handle the potential premature death of the new holder of the focus
457            // (premature death == death before abandoning focus)
458            // Register for client death notification
459            AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
460            try {
461                cb.linkToDeath(afdh, 0);
462            } catch (RemoteException e) {
463                // client has already died!
464                Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
465                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
466            }
467
468            if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
469                // if focus is already owned by this client and the reason for acquiring the focus
470                // hasn't changed, don't do anything
471                if (mFocusStack.peek().getGainRequest() == focusChangeHint) {
472                    // unlink death handler so it can be gc'ed.
473                    // linkToDeath() creates a JNI global reference preventing collection.
474                    cb.unlinkToDeath(afdh, 0);
475                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
476                }
477                // the reason for the audio focus request has changed: remove the current top of
478                // stack and respond as if we had a new focus owner
479                FocusRequester fr = mFocusStack.pop();
480                fr.release();
481            }
482
483            // focus requester might already be somewhere below in the stack, remove it
484            removeFocusStackEntry(clientId, false /* signal */);
485
486            // propagate the focus change through the stack
487            if (!mFocusStack.empty()) {
488                propagateFocusLossFromGain_syncAf(focusChangeHint);
489            }
490
491            // push focus requester at the top of the audio focus stack
492            mFocusStack.push(new FocusRequester(mainStreamType, focusChangeHint, fd, cb,
493                    clientId, afdh, callingPackageName, Binder.getCallingUid()));
494
495            // there's a new top of the stack, let the remote control know
496            synchronized(mRCStack) {
497                checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
498            }
499        }//synchronized(mAudioFocusLock)
500
501        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
502    }
503
504    /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener)  */
505    protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) {
506        Log.i(TAG, " AudioFocus  abandonAudioFocus() from " + clientId);
507        try {
508            // this will take care of notifying the new focus owner if needed
509            synchronized(mAudioFocusLock) {
510                removeFocusStackEntry(clientId, true /*signal*/);
511            }
512        } catch (java.util.ConcurrentModificationException cme) {
513            // Catching this exception here is temporary. It is here just to prevent
514            // a crash seen when the "Silent" notification is played. This is believed to be fixed
515            // but this try catch block is left just to be safe.
516            Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
517            cme.printStackTrace();
518        }
519
520        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
521    }
522
523
524    protected void unregisterAudioFocusClient(String clientId) {
525        synchronized(mAudioFocusLock) {
526            removeFocusStackEntry(clientId, false);
527        }
528    }
529
530
531    //==========================================================================================
532    // RemoteControl
533    //==========================================================================================
534    /**
535     * No-op if the key code for keyEvent is not a valid media key
536     * (see {@link #isValidMediaKeyEvent(KeyEvent)})
537     * @param keyEvent the key event to send
538     */
539    protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
540        filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
541    }
542
543    /**
544     * No-op if the key code for keyEvent is not a valid media key
545     * (see {@link #isValidMediaKeyEvent(KeyEvent)})
546     * @param keyEvent the key event to send
547     */
548    protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
549        filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
550    }
551
552    private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
553        // sanity check on the incoming key event
554        if (!isValidMediaKeyEvent(keyEvent)) {
555            Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
556            return;
557        }
558        // event filtering for telephony
559        synchronized(mRingingLock) {
560            synchronized(mRCStack) {
561                if ((mMediaReceiverForCalls != null) &&
562                        (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
563                    dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
564                    return;
565                }
566            }
567        }
568        // event filtering based on voice-based interactions
569        if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
570            filterVoiceInputKeyEvent(keyEvent, needWakeLock);
571        } else {
572            dispatchMediaKeyEvent(keyEvent, needWakeLock);
573        }
574    }
575
576    /**
577     * Handles the dispatching of the media button events to the telephony package.
578     * Precondition: mMediaReceiverForCalls != null
579     * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
580     * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
581     *     is dispatched.
582     */
583    private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) {
584        Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
585        keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
586        keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
587        if (needWakeLock) {
588            mMediaEventWakeLock.acquire();
589            keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
590        }
591        final long ident = Binder.clearCallingIdentity();
592        try {
593            mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
594                    null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null);
595        } finally {
596            Binder.restoreCallingIdentity(ident);
597        }
598    }
599
600    /**
601     * Handles the dispatching of the media button events to one of the registered listeners,
602     * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
603     * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
604     * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
605     *     is dispatched.
606     */
607    private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
608        if (needWakeLock) {
609            mMediaEventWakeLock.acquire();
610        }
611        Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
612        keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
613        synchronized(mRCStack) {
614            if (!mRCStack.empty()) {
615                // send the intent that was registered by the client
616                try {
617                    mRCStack.peek().mMediaIntent.send(mContext,
618                            needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
619                            keyIntent, this, mEventHandler);
620                } catch (CanceledException e) {
621                    Log.e(TAG, "Error sending pending intent " + mRCStack.peek());
622                    e.printStackTrace();
623                }
624            } else {
625                // legacy behavior when nobody registered their media button event receiver
626                //    through AudioManager
627                if (needWakeLock) {
628                    keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
629                }
630                final long ident = Binder.clearCallingIdentity();
631                try {
632                    mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
633                            null, mKeyEventDone,
634                            mEventHandler, Activity.RESULT_OK, null, null);
635                } finally {
636                    Binder.restoreCallingIdentity(ident);
637                }
638            }
639        }
640    }
641
642    /**
643     * The different actions performed in response to a voice button key event.
644     */
645    private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
646    private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
647    private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
648
649    private final Object mVoiceEventLock = new Object();
650    private boolean mVoiceButtonDown;
651    private boolean mVoiceButtonHandled;
652
653    /**
654     * Filter key events that may be used for voice-based interactions
655     * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
656     *    media buttons that can be used to trigger voice-based interactions.
657     * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
658     *     is dispatched.
659     */
660    private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
661        if (DEBUG_RC) {
662            Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
663        }
664
665        int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
666        int keyAction = keyEvent.getAction();
667        synchronized (mVoiceEventLock) {
668            if (keyAction == KeyEvent.ACTION_DOWN) {
669                if (keyEvent.getRepeatCount() == 0) {
670                    // initial down
671                    mVoiceButtonDown = true;
672                    mVoiceButtonHandled = false;
673                } else if (mVoiceButtonDown && !mVoiceButtonHandled
674                        && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
675                    // long-press, start voice-based interactions
676                    mVoiceButtonHandled = true;
677                    voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
678                }
679            } else if (keyAction == KeyEvent.ACTION_UP) {
680                if (mVoiceButtonDown) {
681                    // voice button up
682                    mVoiceButtonDown = false;
683                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
684                        voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
685                    }
686                }
687            }
688        }//synchronized (mVoiceEventLock)
689
690        // take action after media button event filtering for voice-based interactions
691        switch (voiceButtonAction) {
692            case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
693                if (DEBUG_RC) Log.v(TAG, "   ignore key event");
694                break;
695            case VOICEBUTTON_ACTION_START_VOICE_INPUT:
696                if (DEBUG_RC) Log.v(TAG, "   start voice-based interactions");
697                // then start the voice-based interactions
698                startVoiceBasedInteractions(needWakeLock);
699                break;
700            case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
701                if (DEBUG_RC) Log.v(TAG, "   send simulated key event, wakelock=" + needWakeLock);
702                sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
703                break;
704        }
705    }
706
707    private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) {
708        // send DOWN event
709        KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
710        dispatchMediaKeyEvent(keyEvent, needWakeLock);
711        // send UP event
712        keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
713        dispatchMediaKeyEvent(keyEvent, needWakeLock);
714
715    }
716
717    private class PackageIntentsReceiver extends BroadcastReceiver {
718        @Override
719        public void onReceive(Context context, Intent intent) {
720            String action = intent.getAction();
721            if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
722                    || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) {
723                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
724                    // a package is being removed, not replaced
725                    String packageName = intent.getData().getSchemeSpecificPart();
726                    if (packageName != null) {
727                        cleanupMediaButtonReceiverForPackage(packageName, true);
728                    }
729                }
730            } else if (action.equals(Intent.ACTION_PACKAGE_ADDED)
731                    || action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
732                String packageName = intent.getData().getSchemeSpecificPart();
733                if (packageName != null) {
734                    cleanupMediaButtonReceiverForPackage(packageName, false);
735                }
736            }
737        }
738    }
739
740    protected static boolean isMediaKeyCode(int keyCode) {
741        switch (keyCode) {
742            case KeyEvent.KEYCODE_MUTE:
743            case KeyEvent.KEYCODE_HEADSETHOOK:
744            case KeyEvent.KEYCODE_MEDIA_PLAY:
745            case KeyEvent.KEYCODE_MEDIA_PAUSE:
746            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
747            case KeyEvent.KEYCODE_MEDIA_STOP:
748            case KeyEvent.KEYCODE_MEDIA_NEXT:
749            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
750            case KeyEvent.KEYCODE_MEDIA_REWIND:
751            case KeyEvent.KEYCODE_MEDIA_RECORD:
752            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
753            case KeyEvent.KEYCODE_MEDIA_CLOSE:
754            case KeyEvent.KEYCODE_MEDIA_EJECT:
755            case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
756                return true;
757            default:
758                return false;
759        }
760    }
761
762    private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
763        if (keyEvent == null) {
764            return false;
765        }
766        return MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode());
767    }
768
769    /**
770     * Checks whether the given key code is one that can trigger the launch of voice-based
771     *   interactions.
772     * @param keyCode the key code associated with the key event
773     * @return true if the key is one of the supported voice-based interaction triggers
774     */
775    private static boolean isValidVoiceInputKeyCode(int keyCode) {
776        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
777            return true;
778        } else {
779            return false;
780        }
781    }
782
783    /**
784     * Tell the system to start voice-based interactions / voice commands
785     */
786    private void startVoiceBasedInteractions(boolean needWakeLock) {
787        Intent voiceIntent = null;
788        // select which type of search to launch:
789        // - screen on and device unlocked: action is ACTION_WEB_SEARCH
790        // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
791        //    with EXTRA_SECURE set to true if the device is securely locked
792        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
793        boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
794        if (!isLocked && pm.isScreenOn()) {
795            voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
796            Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
797        } else {
798            voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
799            voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
800                    isLocked && mKeyguardManager.isKeyguardSecure());
801            Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
802        }
803        // start the search activity
804        if (needWakeLock) {
805            mMediaEventWakeLock.acquire();
806        }
807        final long identity = Binder.clearCallingIdentity();
808        try {
809            if (voiceIntent != null) {
810                voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
811                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
812                mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
813            }
814        } catch (ActivityNotFoundException e) {
815            Log.w(TAG, "No activity for search: " + e);
816        } finally {
817            Binder.restoreCallingIdentity(identity);
818            if (needWakeLock) {
819                mMediaEventWakeLock.release();
820            }
821        }
822    }
823
824    private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
825
826    // only set when wakelock was acquired, no need to check value when received
827    private static final String EXTRA_WAKELOCK_ACQUIRED =
828            "android.media.AudioService.WAKELOCK_ACQUIRED";
829
830    public void onSendFinished(PendingIntent pendingIntent, Intent intent,
831            int resultCode, String resultData, Bundle resultExtras) {
832        if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
833            mMediaEventWakeLock.release();
834        }
835    }
836
837    BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
838        public void onReceive(Context context, Intent intent) {
839            if (intent == null) {
840                return;
841            }
842            Bundle extras = intent.getExtras();
843            if (extras == null) {
844                return;
845            }
846            if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
847                mMediaEventWakeLock.release();
848            }
849        }
850    };
851
852    /**
853     * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack
854     */
855    private final Object mCurrentRcLock = new Object();
856    /**
857     * The one remote control client which will receive a request for display information.
858     * This object may be null.
859     * Access protected by mCurrentRcLock.
860     */
861    private IRemoteControlClient mCurrentRcClient = null;
862    /**
863     * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant
864     * if mCurrentRcClient is null
865     */
866    private PendingIntent mCurrentRcClientIntent = null;
867
868    private final static int RC_INFO_NONE = 0;
869    private final static int RC_INFO_ALL =
870        RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
871        RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
872        RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
873        RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
874
875    /**
876     * A monotonically increasing generation counter for mCurrentRcClient.
877     * Only accessed with a lock on mCurrentRcLock.
878     * No value wrap-around issues as we only act on equal values.
879     */
880    private int mCurrentRcClientGen = 0;
881
882    /**
883     * Inner class to monitor remote control client deaths, and remove the client for the
884     * remote control stack if necessary.
885     */
886    private class RcClientDeathHandler implements IBinder.DeathRecipient {
887        final private IBinder mCb; // To be notified of client's death
888        final private PendingIntent mMediaIntent;
889
890        RcClientDeathHandler(IBinder cb, PendingIntent pi) {
891            mCb = cb;
892            mMediaIntent = pi;
893        }
894
895        public void binderDied() {
896            Log.w(TAG, "  RemoteControlClient died");
897            // remote control client died, make sure the displays don't use it anymore
898            //  by setting its remote control client to null
899            registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
900            // the dead client was maybe handling remote playback, reevaluate
901            postReevaluateRemote();
902        }
903
904        public IBinder getBinder() {
905            return mCb;
906        }
907    }
908
909    /**
910     * A global counter for RemoteControlClient identifiers
911     */
912    private static int sLastRccId = 0;
913
914    private class RemotePlaybackState {
915        int mRccId;
916        int mVolume;
917        int mVolumeMax;
918        int mVolumeHandling;
919
920        private RemotePlaybackState(int id, int vol, int volMax) {
921            mRccId = id;
922            mVolume = vol;
923            mVolumeMax = volMax;
924            mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
925        }
926    }
927
928    /**
929     * Internal cache for the playback information of the RemoteControlClient whose volume gets to
930     * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
931     * every time we need this info.
932     */
933    private RemotePlaybackState mMainRemote;
934    /**
935     * Indicates whether the "main" RemoteControlClient is considered active.
936     * Use synchronized on mMainRemote.
937     */
938    private boolean mMainRemoteIsActive;
939    /**
940     * Indicates whether there is remote playback going on. True even if there is no "active"
941     * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
942     * handles remote playback.
943     * Use synchronized on mMainRemote.
944     */
945    private boolean mHasRemotePlayback;
946
947    private static class RccPlaybackState {
948        public int mState;
949        public long mPositionMs;
950        public float mSpeed;
951
952        public RccPlaybackState(int state, long positionMs, float speed) {
953            mState = state;
954            mPositionMs = positionMs;
955            mSpeed = speed;
956        }
957
958        public void reset() {
959            mState = RemoteControlClient.PLAYSTATE_STOPPED;
960            mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
961            mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
962        }
963
964        @Override
965        public String toString() {
966            return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
967        }
968
969        private String posToString() {
970            if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
971                return "PLAYBACK_POSITION_INVALID";
972            } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
973                return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
974            } else {
975                return (String.valueOf(mPositionMs) + "ms");
976            }
977        }
978
979        private String stateToString() {
980            switch (mState) {
981                case RemoteControlClient.PLAYSTATE_NONE:
982                    return "PLAYSTATE_NONE";
983                case RemoteControlClient.PLAYSTATE_STOPPED:
984                    return "PLAYSTATE_STOPPED";
985                case RemoteControlClient.PLAYSTATE_PAUSED:
986                    return "PLAYSTATE_PAUSED";
987                case RemoteControlClient.PLAYSTATE_PLAYING:
988                    return "PLAYSTATE_PLAYING";
989                case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
990                    return "PLAYSTATE_FAST_FORWARDING";
991                case RemoteControlClient.PLAYSTATE_REWINDING:
992                    return "PLAYSTATE_REWINDING";
993                case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
994                    return "PLAYSTATE_SKIPPING_FORWARDS";
995                case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
996                    return "PLAYSTATE_SKIPPING_BACKWARDS";
997                case RemoteControlClient.PLAYSTATE_BUFFERING:
998                    return "PLAYSTATE_BUFFERING";
999                case RemoteControlClient.PLAYSTATE_ERROR:
1000                    return "PLAYSTATE_ERROR";
1001                default:
1002                    return "[invalid playstate]";
1003            }
1004        }
1005    }
1006
1007    protected static class RemoteControlStackEntry implements DeathRecipient {
1008        public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
1009        final public MediaFocusControl mController;
1010        /**
1011         * The target for the ACTION_MEDIA_BUTTON events.
1012         * Always non null.
1013         */
1014        final public PendingIntent mMediaIntent;
1015        /**
1016         * The registered media button event receiver.
1017         * Always non null.
1018         */
1019        final public ComponentName mReceiverComponent;
1020        public IBinder mToken;
1021        public String mCallingPackageName;
1022        public int mCallingUid;
1023        /**
1024         * Provides access to the information to display on the remote control.
1025         * May be null (when a media button event receiver is registered,
1026         *     but no remote control client has been registered) */
1027        public IRemoteControlClient mRcClient;
1028        public RcClientDeathHandler mRcClientDeathHandler;
1029        /**
1030         * Information only used for non-local playback
1031         */
1032        public int mPlaybackType;
1033        public int mPlaybackVolume;
1034        public int mPlaybackVolumeMax;
1035        public int mPlaybackVolumeHandling;
1036        public int mPlaybackStream;
1037        public RccPlaybackState mPlaybackState;
1038        public IRemoteVolumeObserver mRemoteVolumeObs;
1039
1040        public void resetPlaybackInfo() {
1041            mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
1042            mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
1043            mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
1044            mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
1045            mPlaybackStream = AudioManager.STREAM_MUSIC;
1046            mPlaybackState.reset();
1047            mRemoteVolumeObs = null;
1048        }
1049
1050        /** precondition: mediaIntent != null */
1051        public RemoteControlStackEntry(MediaFocusControl controller, PendingIntent mediaIntent,
1052                ComponentName eventReceiver, IBinder token) {
1053            mController = controller;
1054            mMediaIntent = mediaIntent;
1055            mReceiverComponent = eventReceiver;
1056            mToken = token;
1057            mCallingUid = -1;
1058            mRcClient = null;
1059            mRccId = ++sLastRccId;
1060            mPlaybackState = new RccPlaybackState(
1061                    RemoteControlClient.PLAYSTATE_STOPPED,
1062                    RemoteControlClient.PLAYBACK_POSITION_INVALID,
1063                    RemoteControlClient.PLAYBACK_SPEED_1X);
1064
1065            resetPlaybackInfo();
1066            if (mToken != null) {
1067                try {
1068                    mToken.linkToDeath(this, 0);
1069                } catch (RemoteException e) {
1070                    mController.mEventHandler.post(new Runnable() {
1071                        @Override public void run() {
1072                            mController.unregisterMediaButtonIntent(mMediaIntent);
1073                        }
1074                    });
1075                }
1076            }
1077        }
1078
1079        public void unlinkToRcClientDeath() {
1080            if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
1081                try {
1082                    mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
1083                    mRcClientDeathHandler = null;
1084                } catch (java.util.NoSuchElementException e) {
1085                    // not much we can do here
1086                    Log.e(TAG, "Encountered " + e + " in unlinkToRcClientDeath()");
1087                    e.printStackTrace();
1088                }
1089            }
1090        }
1091
1092        public void destroy() {
1093            unlinkToRcClientDeath();
1094            if (mToken != null) {
1095                mToken.unlinkToDeath(this, 0);
1096                mToken = null;
1097            }
1098        }
1099
1100        @Override
1101        public void binderDied() {
1102            mController.unregisterMediaButtonIntent(mMediaIntent);
1103        }
1104
1105        @Override
1106        protected void finalize() throws Throwable {
1107            destroy(); // unlink exception handled inside method
1108            super.finalize();
1109        }
1110    }
1111
1112    /**
1113     *  The stack of remote control event receivers.
1114     *  Code sections and methods that modify the remote control event receiver stack are
1115     *  synchronized on mRCStack, but also BEFORE on mFocusLock as any change in either
1116     *  stack, audio focus or RC, can lead to a change in the remote control display
1117     */
1118    private final Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>();
1119
1120    /**
1121     * The component the telephony package can register so telephony calls have priority to
1122     * handle media button events
1123     */
1124    private ComponentName mMediaReceiverForCalls = null;
1125
1126    /**
1127     * Helper function:
1128     * Display in the log the current entries in the remote control focus stack
1129     */
1130    private void dumpRCStack(PrintWriter pw) {
1131        pw.println("\nRemote Control stack entries (last is top of stack):");
1132        synchronized(mRCStack) {
1133            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
1134            while(stackIterator.hasNext()) {
1135                RemoteControlStackEntry rcse = stackIterator.next();
1136                pw.println("  pi: " + rcse.mMediaIntent +
1137                        " -- pack: " + rcse.mCallingPackageName +
1138                        "  -- ercvr: " + rcse.mReceiverComponent +
1139                        "  -- client: " + rcse.mRcClient +
1140                        "  -- uid: " + rcse.mCallingUid +
1141                        "  -- type: " + rcse.mPlaybackType +
1142                        "  state: " + rcse.mPlaybackState);
1143            }
1144        }
1145    }
1146
1147    /**
1148     * Helper function:
1149     * Display in the log the current entries in the remote control stack, focusing
1150     * on RemoteControlClient data
1151     */
1152    private void dumpRCCStack(PrintWriter pw) {
1153        pw.println("\nRemote Control Client stack entries (last is top of stack):");
1154        synchronized(mRCStack) {
1155            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
1156            while(stackIterator.hasNext()) {
1157                RemoteControlStackEntry rcse = stackIterator.next();
1158                pw.println("  uid: " + rcse.mCallingUid +
1159                        "  -- id: " + rcse.mRccId +
1160                        "  -- type: " + rcse.mPlaybackType +
1161                        "  -- state: " + rcse.mPlaybackState +
1162                        "  -- vol handling: " + rcse.mPlaybackVolumeHandling +
1163                        "  -- vol: " + rcse.mPlaybackVolume +
1164                        "  -- volMax: " + rcse.mPlaybackVolumeMax +
1165                        "  -- volObs: " + rcse.mRemoteVolumeObs);
1166            }
1167            synchronized(mCurrentRcLock) {
1168                pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
1169            }
1170        }
1171        synchronized (mMainRemote) {
1172            pw.println("\nRemote Volume State:");
1173            pw.println("  has remote: " + mHasRemotePlayback);
1174            pw.println("  is remote active: " + mMainRemoteIsActive);
1175            pw.println("  rccId: " + mMainRemote.mRccId);
1176            pw.println("  volume handling: "
1177                    + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
1178                            "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
1179            pw.println("  volume: " + mMainRemote.mVolume);
1180            pw.println("  volume steps: " + mMainRemote.mVolumeMax);
1181        }
1182    }
1183
1184    /**
1185     * Helper function:
1186     * Display in the log the current entries in the list of remote control displays
1187     */
1188    private void dumpRCDList(PrintWriter pw) {
1189        pw.println("\nRemote Control Display list entries:");
1190        synchronized(mRCStack) {
1191            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1192            while (displayIterator.hasNext()) {
1193                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
1194                pw.println("  IRCD: " + di.mRcDisplay +
1195                        "  -- w:" + di.mArtworkExpectedWidth +
1196                        "  -- h:" + di.mArtworkExpectedHeight+
1197                        "  -- wantsPosSync:" + di.mWantsPositionSync);
1198            }
1199        }
1200    }
1201
1202    /**
1203     * Helper function:
1204     * Remove any entry in the remote control stack that has the same package name as packageName
1205     * Pre-condition: packageName != null
1206     */
1207    private void cleanupMediaButtonReceiverForPackage(String packageName, boolean removeAll) {
1208        synchronized(mRCStack) {
1209            if (mRCStack.empty()) {
1210                return;
1211            } else {
1212                final PackageManager pm = mContext.getPackageManager();
1213                RemoteControlStackEntry oldTop = mRCStack.peek();
1214                Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
1215                // iterate over the stack entries
1216                // (using an iterator on the stack so we can safely remove an entry after having
1217                //  evaluated it, traversal order doesn't matter here)
1218                while(stackIterator.hasNext()) {
1219                    RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
1220                    if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) {
1221                        // a stack entry is from the package being removed, remove it from the stack
1222                        stackIterator.remove();
1223                        rcse.destroy();
1224                    } else if (rcse.mReceiverComponent != null) {
1225                        try {
1226                            // Check to see if this receiver still exists.
1227                            pm.getReceiverInfo(rcse.mReceiverComponent, 0);
1228                        } catch (PackageManager.NameNotFoundException e) {
1229                            // Not found -- remove it!
1230                            stackIterator.remove();
1231                            rcse.destroy();
1232                        }
1233                    }
1234                }
1235                if (mRCStack.empty()) {
1236                    // no saved media button receiver
1237                    mEventHandler.sendMessage(
1238                            mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
1239                                    null));
1240                } else if (oldTop != mRCStack.peek()) {
1241                    // the top of the stack has changed, save it in the system settings
1242                    // by posting a message to persist it; only do this however if it has
1243                    // a concrete component name (is not a transient registration)
1244                    RemoteControlStackEntry rcse = mRCStack.peek();
1245                    if (rcse.mReceiverComponent != null) {
1246                        mEventHandler.sendMessage(
1247                                mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
1248                                        rcse.mReceiverComponent));
1249                    }
1250                }
1251            }
1252        }
1253    }
1254
1255    /**
1256     * Helper function:
1257     * Restore remote control receiver from the system settings.
1258     */
1259    protected void restoreMediaButtonReceiver() {
1260        String receiverName = Settings.System.getStringForUser(mContentResolver,
1261                Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT);
1262        if ((null != receiverName) && !receiverName.isEmpty()) {
1263            ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName);
1264            if (eventReceiver == null) {
1265                // an invalid name was persisted
1266                return;
1267            }
1268            // construct a PendingIntent targeted to the restored component name
1269            // for the media button and register it
1270            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1271            //     the associated intent will be handled by the component being registered
1272            mediaButtonIntent.setComponent(eventReceiver);
1273            PendingIntent pi = PendingIntent.getBroadcast(mContext,
1274                    0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
1275            registerMediaButtonIntent(pi, eventReceiver, null);
1276        }
1277    }
1278
1279    /**
1280     * Helper function:
1281     * Set the new remote control receiver at the top of the RC focus stack.
1282     * Called synchronized on mAudioFocusLock, then mRCStack
1283     * precondition: mediaIntent != null
1284     */
1285    private void pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent, ComponentName target,
1286            IBinder token) {
1287        // already at top of stack?
1288        if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) {
1289            return;
1290        }
1291        if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
1292                mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
1293            return;
1294        }
1295        RemoteControlStackEntry rcse = null;
1296        boolean wasInsideStack = false;
1297        try {
1298            for (int index = mRCStack.size()-1; index >= 0; index--) {
1299                rcse = mRCStack.elementAt(index);
1300                if(rcse.mMediaIntent.equals(mediaIntent)) {
1301                    // ok to remove element while traversing the stack since we're leaving the loop
1302                    mRCStack.removeElementAt(index);
1303                    wasInsideStack = true;
1304                    break;
1305                }
1306            }
1307        } catch (ArrayIndexOutOfBoundsException e) {
1308            // not expected to happen, indicates improper concurrent modification
1309            Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
1310        }
1311        if (!wasInsideStack) {
1312            rcse = new RemoteControlStackEntry(this, mediaIntent, target, token);
1313        }
1314        mRCStack.push(rcse); // rcse is never null
1315
1316        // post message to persist the default media button receiver
1317        if (target != null) {
1318            mEventHandler.sendMessage( mEventHandler.obtainMessage(
1319                    MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
1320        }
1321    }
1322
1323    /**
1324     * Helper function:
1325     * Remove the remote control receiver from the RC focus stack.
1326     * Called synchronized on mAudioFocusLock, then mRCStack
1327     * precondition: pi != null
1328     */
1329    private void removeMediaButtonReceiver_syncAfRcs(PendingIntent pi) {
1330        try {
1331            for (int index = mRCStack.size()-1; index >= 0; index--) {
1332                final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
1333                if (rcse.mMediaIntent.equals(pi)) {
1334                    rcse.destroy();
1335                    // ok to remove element while traversing the stack since we're leaving the loop
1336                    mRCStack.removeElementAt(index);
1337                    break;
1338                }
1339            }
1340        } catch (ArrayIndexOutOfBoundsException e) {
1341            // not expected to happen, indicates improper concurrent modification
1342            Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
1343        }
1344    }
1345
1346    /**
1347     * Helper function:
1348     * Called synchronized on mRCStack
1349     */
1350    private boolean isCurrentRcController(PendingIntent pi) {
1351        if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) {
1352            return true;
1353        }
1354        return false;
1355    }
1356
1357    private void onHandlePersistMediaButtonReceiver(ComponentName receiver) {
1358        Settings.System.putStringForUser(mContentResolver,
1359                                         Settings.System.MEDIA_BUTTON_RECEIVER,
1360                                         receiver == null ? "" : receiver.flattenToString(),
1361                                         UserHandle.USER_CURRENT);
1362    }
1363
1364    //==========================================================================================
1365    // Remote control display / client
1366    //==========================================================================================
1367    /**
1368     * Update the remote control displays with the new "focused" client generation
1369     */
1370    private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
1371            PendingIntent newMediaIntent, boolean clearing) {
1372        synchronized(mRCStack) {
1373            if (mRcDisplays.size() > 0) {
1374                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1375                while (displayIterator.hasNext()) {
1376                    final DisplayInfoForServer di = displayIterator.next();
1377                    try {
1378                        di.mRcDisplay.setCurrentClientId(
1379                                newClientGeneration, newMediaIntent, clearing);
1380                    } catch (RemoteException e) {
1381                        Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
1382                        di.release();
1383                        displayIterator.remove();
1384                    }
1385                }
1386            }
1387        }
1388    }
1389
1390    /**
1391     * Update the remote control clients with the new "focused" client generation
1392     */
1393    private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) {
1394        // (using an iterator on the stack so we can safely remove an entry if needed,
1395        //  traversal order doesn't matter here as we update all entries)
1396        Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
1397        while(stackIterator.hasNext()) {
1398            RemoteControlStackEntry se = stackIterator.next();
1399            if ((se != null) && (se.mRcClient != null)) {
1400                try {
1401                    se.mRcClient.setCurrentClientGenerationId(newClientGeneration);
1402                } catch (RemoteException e) {
1403                    Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
1404                    stackIterator.remove();
1405                    se.unlinkToRcClientDeath();
1406                }
1407            }
1408        }
1409    }
1410
1411    /**
1412     * Update the displays and clients with the new "focused" client generation and name
1413     * @param newClientGeneration the new generation value matching a client update
1414     * @param newMediaIntent the media button event receiver associated with the client.
1415     *    May be null, which implies there is no registered media button event receiver.
1416     * @param clearing true if the new client generation value maps to a remote control update
1417     *    where the display should be cleared.
1418     */
1419    private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
1420            PendingIntent newMediaIntent, boolean clearing) {
1421        // send the new valid client generation ID to all displays
1422        setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
1423        // send the new valid client generation ID to all clients
1424        setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
1425    }
1426
1427    /**
1428     * Called when processing MSG_RCDISPLAY_CLEAR event
1429     */
1430    private void onRcDisplayClear() {
1431        if (DEBUG_RC) Log.i(TAG, "Clear remote control display");
1432
1433        synchronized(mRCStack) {
1434            synchronized(mCurrentRcLock) {
1435                mCurrentRcClientGen++;
1436                // synchronously update the displays and clients with the new client generation
1437                setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
1438                        null /*newMediaIntent*/, true /*clearing*/);
1439            }
1440        }
1441    }
1442
1443    /**
1444     * Called when processing MSG_RCDISPLAY_UPDATE event
1445     */
1446    private void onRcDisplayUpdate(RemoteControlStackEntry rcse, int flags /* USED ?*/) {
1447        synchronized(mRCStack) {
1448            synchronized(mCurrentRcLock) {
1449                if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(rcse.mRcClient))) {
1450                    if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");
1451
1452                    mCurrentRcClientGen++;
1453                    // synchronously update the displays and clients with
1454                    //      the new client generation
1455                    setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
1456                            rcse.mMediaIntent /*newMediaIntent*/,
1457                            false /*clearing*/);
1458
1459                    // tell the current client that it needs to send info
1460                    try {
1461                        //TODO change name to informationRequestForAllDisplays()
1462                        mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
1463                    } catch (RemoteException e) {
1464                        Log.e(TAG, "Current valid remote client is dead: "+e);
1465                        mCurrentRcClient = null;
1466                    }
1467                } else {
1468                    // the remote control display owner has changed between the
1469                    // the message to update the display was sent, and the time it
1470                    // gets to be processed (now)
1471                }
1472            }
1473        }
1474    }
1475
1476    /**
1477     * Called when processing MSG_RCDISPLAY_INIT_INFO event
1478     * Causes the current RemoteControlClient to send its info (metadata, playstate...) to
1479     *   a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.
1480     */
1481    private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) {
1482        synchronized(mRCStack) {
1483            synchronized(mCurrentRcLock) {
1484                if (mCurrentRcClient != null) {
1485                    if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
1486                    try {
1487                        // synchronously update the new RCD with the current client generation
1488                        // and matching PendingIntent
1489                        newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent,
1490                                false);
1491
1492                        // tell the current RCC that it needs to send info, but only to the new RCD
1493                        try {
1494                            mCurrentRcClient.informationRequestForDisplay(newRcd, w, h);
1495                        } catch (RemoteException e) {
1496                            Log.e(TAG, "Current valid remote client is dead: ", e);
1497                            mCurrentRcClient = null;
1498                        }
1499                    } catch (RemoteException e) {
1500                        Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e);
1501                    }
1502                }
1503            }
1504        }
1505    }
1506
1507    /**
1508     * Helper function:
1509     * Called synchronized on mRCStack
1510     */
1511    private void clearRemoteControlDisplay_syncAfRcs() {
1512        synchronized(mCurrentRcLock) {
1513            mCurrentRcClient = null;
1514        }
1515        // will cause onRcDisplayClear() to be called in AudioService's handler thread
1516        mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
1517    }
1518
1519    /**
1520     * Helper function for code readability: only to be called from
1521     *    checkUpdateRemoteControlDisplay_syncAfRcs() which checks the preconditions for
1522     *    this method.
1523     * Preconditions:
1524     *    - called synchronized mAudioFocusLock then on mRCStack
1525     *    - mRCStack.isEmpty() is false
1526     */
1527    private void updateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) {
1528        RemoteControlStackEntry rcse = mRCStack.peek();
1529        int infoFlagsAboutToBeUsed = infoChangedFlags;
1530        // this is where we enforce opt-in for information display on the remote controls
1531        //   with the new AudioManager.registerRemoteControlClient() API
1532        if (rcse.mRcClient == null) {
1533            //Log.w(TAG, "Can't update remote control display with null remote control client");
1534            clearRemoteControlDisplay_syncAfRcs();
1535            return;
1536        }
1537        synchronized(mCurrentRcLock) {
1538            if (!rcse.mRcClient.equals(mCurrentRcClient)) {
1539                // new RC client, assume every type of information shall be queried
1540                infoFlagsAboutToBeUsed = RC_INFO_ALL;
1541            }
1542            mCurrentRcClient = rcse.mRcClient;
1543            mCurrentRcClientIntent = rcse.mMediaIntent;
1544        }
1545        // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
1546        mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
1547                infoFlagsAboutToBeUsed /* arg1 */, 0, rcse /* obj, != null */) );
1548    }
1549
1550    /**
1551     * Helper function:
1552     * Called synchronized on mAudioFocusLock, then mRCStack
1553     * Check whether the remote control display should be updated, triggers the update if required
1554     * @param infoChangedFlags the flags corresponding to the remote control client information
1555     *     that has changed, if applicable (checking for the update conditions might trigger a
1556     *     clear, rather than an update event).
1557     */
1558    private void checkUpdateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) {
1559        // determine whether the remote control display should be refreshed
1560        // if either stack is empty, there is a mismatch, so clear the RC display
1561        if (mRCStack.isEmpty() || mFocusStack.isEmpty()) {
1562            clearRemoteControlDisplay_syncAfRcs();
1563            return;
1564        }
1565
1566        // determine which entry in the AudioFocus stack to consider, and compare against the
1567        // top of the stack for the media button event receivers : simply using the top of the
1568        // stack would make the entry disappear from the RemoteControlDisplay in conditions such as
1569        // notifications playing during music playback.
1570        // Crawl the AudioFocus stack from the top until an entry is found with the following
1571        // characteristics:
1572        // - focus gain on STREAM_MUSIC stream
1573        // - non-transient focus gain on a stream other than music
1574        FocusRequester af = null;
1575        try {
1576            for (int index = mFocusStack.size()-1; index >= 0; index--) {
1577                FocusRequester fr = mFocusStack.elementAt(index);
1578                if ((fr.getStreamType() == AudioManager.STREAM_MUSIC)
1579                        || (fr.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN)) {
1580                    af = fr;
1581                    break;
1582                }
1583            }
1584        } catch (ArrayIndexOutOfBoundsException e) {
1585            Log.e(TAG, "Wrong index accessing audio focus stack when updating RCD: " + e);
1586            af = null;
1587        }
1588        if (af == null) {
1589            clearRemoteControlDisplay_syncAfRcs();
1590            return;
1591        }
1592
1593        // if the audio focus and RC owners belong to different packages, there is a mismatch, clear
1594        if (!af.hasSamePackage(mRCStack.peek().mCallingPackageName)) {
1595            clearRemoteControlDisplay_syncAfRcs();
1596            return;
1597        }
1598        // if the audio focus didn't originate from the same Uid as the one in which the remote
1599        //   control information will be retrieved, clear
1600        if (!af.hasSameUid(mRCStack.peek().mCallingUid)) {
1601            clearRemoteControlDisplay_syncAfRcs();
1602            return;
1603        }
1604
1605        // refresh conditions were verified: update the remote controls
1606        // ok to call: synchronized mAudioFocusLock then on mRCStack, mRCStack is not empty
1607        updateRemoteControlDisplay_syncAfRcs(infoChangedFlags);
1608    }
1609
1610    /**
1611     * Helper function:
1612     * Post a message to asynchronously move the media button event receiver associated with the
1613     * given remote control client ID to the top of the remote control stack
1614     * @param rccId
1615     */
1616    private void postPromoteRcc(int rccId) {
1617        sendMsg(mEventHandler, MSG_PROMOTE_RCC, SENDMSG_REPLACE,
1618                rccId /*arg1*/, 0, null, 0/*delay*/);
1619    }
1620
1621    private void onPromoteRcc(int rccId) {
1622        if (DEBUG_RC) { Log.d(TAG, "Promoting RCC " + rccId); }
1623        synchronized(mAudioFocusLock) {
1624            synchronized(mRCStack) {
1625                // ignore if given RCC ID is already at top of remote control stack
1626                if (!mRCStack.isEmpty() && (mRCStack.peek().mRccId == rccId)) {
1627                    return;
1628                }
1629                int indexToPromote = -1;
1630                try {
1631                    for (int index = mRCStack.size()-1; index >= 0; index--) {
1632                        final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
1633                        if (rcse.mRccId == rccId) {
1634                            indexToPromote = index;
1635                            break;
1636                        }
1637                    }
1638                    if (indexToPromote >= 0) {
1639                        if (DEBUG_RC) { Log.d(TAG, "  moving RCC from index " + indexToPromote
1640                                + " to " + (mRCStack.size()-1)); }
1641                        final RemoteControlStackEntry rcse = mRCStack.remove(indexToPromote);
1642                        mRCStack.push(rcse);
1643                        // the RC stack changed, reevaluate the display
1644                        checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
1645                    }
1646                } catch (ArrayIndexOutOfBoundsException e) {
1647                    // not expected to happen, indicates improper concurrent modification
1648                    Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
1649                }
1650            }//synchronized(mRCStack)
1651        }//synchronized(mAudioFocusLock)
1652    }
1653
1654    /**
1655     * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
1656     * precondition: mediaIntent != null
1657     */
1658    protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver,
1659            IBinder token) {
1660        Log.i(TAG, "  Remote Control   registerMediaButtonIntent() for " + mediaIntent);
1661
1662        synchronized(mAudioFocusLock) {
1663            synchronized(mRCStack) {
1664                pushMediaButtonReceiver_syncAfRcs(mediaIntent, eventReceiver, token);
1665                // new RC client, assume every type of information shall be queried
1666                checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
1667            }
1668        }
1669    }
1670
1671    /**
1672     * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
1673     * precondition: mediaIntent != null, eventReceiver != null
1674     */
1675    protected void unregisterMediaButtonIntent(PendingIntent mediaIntent)
1676    {
1677        Log.i(TAG, "  Remote Control   unregisterMediaButtonIntent() for " + mediaIntent);
1678
1679        synchronized(mAudioFocusLock) {
1680            synchronized(mRCStack) {
1681                boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
1682                removeMediaButtonReceiver_syncAfRcs(mediaIntent);
1683                if (topOfStackWillChange) {
1684                    // current RC client will change, assume every type of info needs to be queried
1685                    checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
1686                }
1687            }
1688        }
1689    }
1690
1691    /**
1692     * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
1693     * precondition: c != null
1694     */
1695    protected void registerMediaButtonEventReceiverForCalls(ComponentName c) {
1696        if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
1697                != PackageManager.PERMISSION_GRANTED) {
1698            Log.e(TAG, "Invalid permissions to register media button receiver for calls");
1699            return;
1700        }
1701        synchronized(mRCStack) {
1702            mMediaReceiverForCalls = c;
1703        }
1704    }
1705
1706    /**
1707     * see AudioManager.unregisterMediaButtonEventReceiverForCalls()
1708     */
1709    protected void unregisterMediaButtonEventReceiverForCalls() {
1710        if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
1711                != PackageManager.PERMISSION_GRANTED) {
1712            Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
1713            return;
1714        }
1715        synchronized(mRCStack) {
1716            mMediaReceiverForCalls = null;
1717        }
1718    }
1719
1720    /**
1721     * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
1722     * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient
1723     * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
1724     *     without modifying the RC stack, but while still causing the display to refresh (will
1725     *     become blank as a result of this)
1726     */
1727    protected int registerRemoteControlClient(PendingIntent mediaIntent,
1728            IRemoteControlClient rcClient, String callingPackageName) {
1729        if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
1730        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
1731        synchronized(mAudioFocusLock) {
1732            synchronized(mRCStack) {
1733                // store the new display information
1734                try {
1735                    for (int index = mRCStack.size()-1; index >= 0; index--) {
1736                        final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
1737                        if(rcse.mMediaIntent.equals(mediaIntent)) {
1738                            // already had a remote control client?
1739                            if (rcse.mRcClientDeathHandler != null) {
1740                                // stop monitoring the old client's death
1741                                rcse.unlinkToRcClientDeath();
1742                            }
1743                            // save the new remote control client
1744                            rcse.mRcClient = rcClient;
1745                            rcse.mCallingPackageName = callingPackageName;
1746                            rcse.mCallingUid = Binder.getCallingUid();
1747                            if (rcClient == null) {
1748                                // here rcse.mRcClientDeathHandler is null;
1749                                rcse.resetPlaybackInfo();
1750                                break;
1751                            }
1752                            rccId = rcse.mRccId;
1753
1754                            // there is a new (non-null) client:
1755                            // 1/ give the new client the displays (if any)
1756                            if (mRcDisplays.size() > 0) {
1757                                plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient);
1758                            }
1759                            // 2/ monitor the new client's death
1760                            IBinder b = rcse.mRcClient.asBinder();
1761                            RcClientDeathHandler rcdh =
1762                                    new RcClientDeathHandler(b, rcse.mMediaIntent);
1763                            try {
1764                                b.linkToDeath(rcdh, 0);
1765                            } catch (RemoteException e) {
1766                                // remote control client is DOA, disqualify it
1767                                Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
1768                                rcse.mRcClient = null;
1769                            }
1770                            rcse.mRcClientDeathHandler = rcdh;
1771                            break;
1772                        }
1773                    }//for
1774                } catch (ArrayIndexOutOfBoundsException e) {
1775                    // not expected to happen, indicates improper concurrent modification
1776                    Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
1777                }
1778
1779                // if the eventReceiver is at the top of the stack
1780                // then check for potential refresh of the remote controls
1781                if (isCurrentRcController(mediaIntent)) {
1782                    checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
1783                }
1784            }//synchronized(mRCStack)
1785        }//synchronized(mAudioFocusLock)
1786        return rccId;
1787    }
1788
1789    /**
1790     * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
1791     * rcClient is guaranteed non-null
1792     */
1793    protected void unregisterRemoteControlClient(PendingIntent mediaIntent,
1794            IRemoteControlClient rcClient) {
1795        if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
1796        synchronized(mAudioFocusLock) {
1797            synchronized(mRCStack) {
1798                boolean topRccChange = false;
1799                try {
1800                    for (int index = mRCStack.size()-1; index >= 0; index--) {
1801                        final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
1802                        if ((rcse.mMediaIntent.equals(mediaIntent))
1803                                && rcClient.equals(rcse.mRcClient)) {
1804                            // we found the IRemoteControlClient to unregister
1805                            // stop monitoring its death
1806                            rcse.unlinkToRcClientDeath();
1807                            // reset the client-related fields
1808                            rcse.mRcClient = null;
1809                            rcse.mCallingPackageName = null;
1810                            topRccChange = (index == mRCStack.size()-1);
1811                            // there can only be one matching RCC in the RC stack, we're done
1812                            break;
1813                        }
1814                    }
1815                } catch (ArrayIndexOutOfBoundsException e) {
1816                    // not expected to happen, indicates improper concurrent modification
1817                    Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
1818                }
1819                if (topRccChange) {
1820                    // no more RCC for the RCD, check for potential refresh of the remote controls
1821                    checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
1822                }
1823            }
1824        }
1825    }
1826
1827
1828    /**
1829     * A class to encapsulate all the information about a remote control display.
1830     * After instanciation, init() must always be called before the object is added in the list
1831     * of displays.
1832     * Before being removed from the list of displays, release() must always be called (otherwise
1833     * it will leak death handlers).
1834     */
1835    private class DisplayInfoForServer implements IBinder.DeathRecipient {
1836        /** may never be null */
1837        private IRemoteControlDisplay mRcDisplay;
1838        private IBinder mRcDisplayBinder;
1839        private int mArtworkExpectedWidth = -1;
1840        private int mArtworkExpectedHeight = -1;
1841        private boolean mWantsPositionSync = false;
1842
1843        public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
1844            if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
1845            mRcDisplay = rcd;
1846            mRcDisplayBinder = rcd.asBinder();
1847            mArtworkExpectedWidth = w;
1848            mArtworkExpectedHeight = h;
1849        }
1850
1851        public boolean init() {
1852            try {
1853                mRcDisplayBinder.linkToDeath(this, 0);
1854            } catch (RemoteException e) {
1855                // remote control display is DOA, disqualify it
1856                Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
1857                return false;
1858            }
1859            return true;
1860        }
1861
1862        public void release() {
1863            try {
1864                mRcDisplayBinder.unlinkToDeath(this, 0);
1865            } catch (java.util.NoSuchElementException e) {
1866                // not much we can do here, the display should have been unregistered anyway
1867                Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
1868            }
1869        }
1870
1871        public void binderDied() {
1872            synchronized(mRCStack) {
1873                Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
1874                // remove the display from the list
1875                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1876                while (displayIterator.hasNext()) {
1877                    final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
1878                    if (di.mRcDisplay == mRcDisplay) {
1879                        if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
1880                        displayIterator.remove();
1881                        return;
1882                    }
1883                }
1884            }
1885        }
1886    }
1887
1888    /**
1889     * The remote control displays.
1890     * Access synchronized on mRCStack
1891     */
1892    private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
1893
1894    /**
1895     * Plug each registered display into the specified client
1896     * @param rcc, guaranteed non null
1897     */
1898    private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
1899        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1900        while (displayIterator.hasNext()) {
1901            final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
1902            try {
1903                rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
1904                        di.mArtworkExpectedHeight);
1905                if (di.mWantsPositionSync) {
1906                    rcc.setWantsSyncForDisplay(di.mRcDisplay, true);
1907                }
1908            } catch (RemoteException e) {
1909                Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
1910            }
1911        }
1912    }
1913
1914    /**
1915     * Is the remote control display interface already registered
1916     * @param rcd
1917     * @return true if the IRemoteControlDisplay is already in the list of displays
1918     */
1919    private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
1920        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1921        while (displayIterator.hasNext()) {
1922            final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
1923            if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1924                return true;
1925            }
1926        }
1927        return false;
1928    }
1929
1930    /**
1931     * Register an IRemoteControlDisplay.
1932     * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
1933     * at the top of the stack to update the new display with its information.
1934     * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
1935     * @param rcd the IRemoteControlDisplay to register. No effect if null.
1936     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
1937     *   display doesn't need to receive artwork.
1938     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
1939     *   display doesn't need to receive artwork.
1940     */
1941    protected void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
1942        if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
1943        synchronized(mAudioFocusLock) {
1944            synchronized(mRCStack) {
1945                if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
1946                    return;
1947                }
1948                DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
1949                if (!di.init()) {
1950                    if (DEBUG_RC) Log.e(TAG, " error registering RCD");
1951                    return;
1952                }
1953                // add RCD to list of displays
1954                mRcDisplays.add(di);
1955
1956                // let all the remote control clients know there is a new display (so the remote
1957                //   control stack traversal order doesn't matter).
1958                Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
1959                while(stackIterator.hasNext()) {
1960                    RemoteControlStackEntry rcse = stackIterator.next();
1961                    if(rcse.mRcClient != null) {
1962                        try {
1963                            rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h);
1964                        } catch (RemoteException e) {
1965                            Log.e(TAG, "Error connecting RCD to client: ", e);
1966                        }
1967                    }
1968                }
1969
1970                // we have a new display, of which all the clients are now aware: have it be
1971                // initialized wih the current gen ID and the current client info, do not
1972                // reset the information for the other (existing) displays
1973                sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
1974                        w /*arg1*/, h /*arg2*/,
1975                        rcd /*obj*/, 0/*delay*/);
1976            }
1977        }
1978    }
1979
1980    /**
1981     * Unregister an IRemoteControlDisplay.
1982     * No effect if the IRemoteControlDisplay hasn't been successfully registered.
1983     * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
1984     * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
1985     */
1986    protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
1987        if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
1988        synchronized(mRCStack) {
1989            if (rcd == null) {
1990                return;
1991            }
1992
1993            boolean displayWasPluggedIn = false;
1994            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1995            while (displayIterator.hasNext() && !displayWasPluggedIn) {
1996                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
1997                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1998                    displayWasPluggedIn = true;
1999                    di.release();
2000                    displayIterator.remove();
2001                }
2002            }
2003
2004            if (displayWasPluggedIn) {
2005                // disconnect this remote control display from all the clients, so the remote
2006                //   control stack traversal order doesn't matter
2007                final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
2008                while(stackIterator.hasNext()) {
2009                    final RemoteControlStackEntry rcse = stackIterator.next();
2010                    if(rcse.mRcClient != null) {
2011                        try {
2012                            rcse.mRcClient.unplugRemoteControlDisplay(rcd);
2013                        } catch (RemoteException e) {
2014                            Log.e(TAG, "Error disconnecting remote control display to client: ", e);
2015                        }
2016                    }
2017                }
2018            } else {
2019                if (DEBUG_RC) Log.w(TAG, "  trying to unregister unregistered RCD");
2020            }
2021        }
2022    }
2023
2024    /**
2025     * Update the size of the artwork used by an IRemoteControlDisplay.
2026     * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
2027     * @param rcd the IRemoteControlDisplay with the new artwork size requirement
2028     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
2029     *   display doesn't need to receive artwork.
2030     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
2031     *   display doesn't need to receive artwork.
2032     */
2033    protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
2034        synchronized(mRCStack) {
2035            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
2036            boolean artworkSizeUpdate = false;
2037            while (displayIterator.hasNext() && !artworkSizeUpdate) {
2038                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
2039                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
2040                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
2041                        di.mArtworkExpectedWidth = w;
2042                        di.mArtworkExpectedHeight = h;
2043                        artworkSizeUpdate = true;
2044                    }
2045                }
2046            }
2047            if (artworkSizeUpdate) {
2048                // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
2049                // stack traversal order doesn't matter
2050                final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
2051                while(stackIterator.hasNext()) {
2052                    final RemoteControlStackEntry rcse = stackIterator.next();
2053                    if(rcse.mRcClient != null) {
2054                        try {
2055                            rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h);
2056                        } catch (RemoteException e) {
2057                            Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
2058                        }
2059                    }
2060                }
2061            }
2062        }
2063    }
2064
2065    /**
2066     * Controls whether a remote control display needs periodic checks of the RemoteControlClient
2067     * playback position to verify that the estimated position has not drifted from the actual
2068     * position. By default the check is not performed.
2069     * The IRemoteControlDisplay must have been previously registered for this to have any effect.
2070     * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
2071     *     or disabled. Not null.
2072     * @param wantsSync if true, RemoteControlClient instances which expose their playback position
2073     *     to the framework will regularly compare the estimated playback position with the actual
2074     *     position, and will update the IRemoteControlDisplay implementation whenever a drift is
2075     *     detected.
2076     */
2077    protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
2078            boolean wantsSync) {
2079        synchronized(mRCStack) {
2080            boolean rcdRegistered = false;
2081            // store the information about this display
2082            // (display stack traversal order doesn't matter).
2083            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
2084            while (displayIterator.hasNext()) {
2085                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
2086                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
2087                    di.mWantsPositionSync = wantsSync;
2088                    rcdRegistered = true;
2089                    break;
2090                }
2091            }
2092            if (!rcdRegistered) {
2093                return;
2094            }
2095            // notify all current RemoteControlClients
2096            // (stack traversal order doesn't matter as we notify all RCCs)
2097            final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
2098            while (stackIterator.hasNext()) {
2099                final RemoteControlStackEntry rcse = stackIterator.next();
2100                if (rcse.mRcClient != null) {
2101                    try {
2102                        rcse.mRcClient.setWantsSyncForDisplay(rcd, wantsSync);
2103                    } catch (RemoteException e) {
2104                        Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
2105                    }
2106                }
2107            }
2108        }
2109    }
2110
2111    protected void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
2112        // ignore position change requests if invalid generation ID
2113        synchronized(mRCStack) {
2114            synchronized(mCurrentRcLock) {
2115                if (mCurrentRcClientGen != generationId) {
2116                    return;
2117                }
2118            }
2119        }
2120        // discard any unprocessed seek request in the message queue, and replace with latest
2121        sendMsg(mEventHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */,
2122                0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */);
2123    }
2124
2125    private void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
2126        if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId +
2127                ", timeMs=" + timeMs + ")");
2128        synchronized(mRCStack) {
2129            synchronized(mCurrentRcLock) {
2130                if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) {
2131                    // tell the current client to seek to the requested location
2132                    try {
2133                        mCurrentRcClient.seekTo(generationId, timeMs);
2134                    } catch (RemoteException e) {
2135                        Log.e(TAG, "Current valid remote client is dead: "+e);
2136                        mCurrentRcClient = null;
2137                    }
2138                }
2139            }
2140        }
2141    }
2142
2143    protected void updateRemoteControlClientMetadata(int genId, int key, Rating value) {
2144        sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA, SENDMSG_QUEUE,
2145                genId /* arg1 */, key /* arg2 */, value /* obj */, 0 /* delay */);
2146    }
2147
2148    private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) {
2149        if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId +
2150                ", what=" + key + ",rating=" + value + ")");
2151        synchronized(mRCStack) {
2152            synchronized(mCurrentRcLock) {
2153                if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) {
2154                    try {
2155                        switch (key) {
2156                            case MediaMetadataEditor.RATING_KEY_BY_USER:
2157                                mCurrentRcClient.updateMetadata(genId, key, value);
2158                                break;
2159                            default:
2160                                Log.e(TAG, "unhandled metadata key " + key + " update for RCC "
2161                                        + genId);
2162                                break;
2163                        }
2164                    } catch (RemoteException e) {
2165                        Log.e(TAG, "Current valid remote client is dead", e);
2166                        mCurrentRcClient = null;
2167                    }
2168                }
2169            }
2170        }
2171    }
2172
2173    protected void setPlaybackInfoForRcc(int rccId, int what, int value) {
2174        sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE,
2175                rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */);
2176    }
2177
2178    // handler for MSG_RCC_NEW_PLAYBACK_INFO
2179    private void onNewPlaybackInfoForRcc(int rccId, int key, int value) {
2180        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId +
2181                ", what=" + key + ",val=" + value + ")");
2182        synchronized(mRCStack) {
2183            // iterating from top of stack as playback information changes are more likely
2184            //   on entries at the top of the remote control stack
2185            try {
2186                for (int index = mRCStack.size()-1; index >= 0; index--) {
2187                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
2188                    if (rcse.mRccId == rccId) {
2189                        switch (key) {
2190                            case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE:
2191                                rcse.mPlaybackType = value;
2192                                postReevaluateRemote();
2193                                break;
2194                            case RemoteControlClient.PLAYBACKINFO_VOLUME:
2195                                rcse.mPlaybackVolume = value;
2196                                synchronized (mMainRemote) {
2197                                    if (rccId == mMainRemote.mRccId) {
2198                                        mMainRemote.mVolume = value;
2199                                        mVolumeController.postHasNewRemotePlaybackInfo();
2200                                    }
2201                                }
2202                                break;
2203                            case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX:
2204                                rcse.mPlaybackVolumeMax = value;
2205                                synchronized (mMainRemote) {
2206                                    if (rccId == mMainRemote.mRccId) {
2207                                        mMainRemote.mVolumeMax = value;
2208                                        mVolumeController.postHasNewRemotePlaybackInfo();
2209                                    }
2210                                }
2211                                break;
2212                            case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING:
2213                                rcse.mPlaybackVolumeHandling = value;
2214                                synchronized (mMainRemote) {
2215                                    if (rccId == mMainRemote.mRccId) {
2216                                        mMainRemote.mVolumeHandling = value;
2217                                        mVolumeController.postHasNewRemotePlaybackInfo();
2218                                    }
2219                                }
2220                                break;
2221                            case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
2222                                rcse.mPlaybackStream = value;
2223                                break;
2224                            default:
2225                                Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
2226                                break;
2227                        }
2228                        return;
2229                    }
2230                }//for
2231            } catch (ArrayIndexOutOfBoundsException e) {
2232                // not expected to happen, indicates improper concurrent modification
2233                Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e);
2234            }
2235        }
2236    }
2237
2238    protected void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
2239        sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE,
2240                rccId /* arg1 */, state /* arg2 */,
2241                new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */);
2242    }
2243
2244    private void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) {
2245        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state
2246                + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")");
2247        synchronized(mRCStack) {
2248            // iterating from top of stack as playback information changes are more likely
2249            //   on entries at the top of the remote control stack
2250            try {
2251                for (int index = mRCStack.size()-1; index >= 0; index--) {
2252                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
2253                    if (rcse.mRccId == rccId) {
2254                        rcse.mPlaybackState = newState;
2255                        synchronized (mMainRemote) {
2256                            if (rccId == mMainRemote.mRccId) {
2257                                mMainRemoteIsActive = isPlaystateActive(state);
2258                                postReevaluateRemote();
2259                            }
2260                        }
2261                        // an RCC moving to a "playing" state should become the media button
2262                        //   event receiver so it can be controlled, without requiring the
2263                        //   app to re-register its receiver
2264                        if (isPlaystateActive(state)) {
2265                            postPromoteRcc(rccId);
2266                        }
2267                    }
2268                }//for
2269            } catch (ArrayIndexOutOfBoundsException e) {
2270                // not expected to happen, indicates improper concurrent modification
2271                Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e);
2272            }
2273        }
2274    }
2275
2276    protected void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
2277        sendMsg(mEventHandler, MSG_RCC_NEW_VOLUME_OBS, SENDMSG_QUEUE,
2278                rccId /* arg1 */, 0, rvo /* obj */, 0 /* delay */);
2279    }
2280
2281    // handler for MSG_RCC_NEW_VOLUME_OBS
2282    private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
2283        synchronized(mRCStack) {
2284            // The stack traversal order doesn't matter because there is only one stack entry
2285            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
2286            //  start iterating from the top.
2287            try {
2288                for (int index = mRCStack.size()-1; index >= 0; index--) {
2289                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
2290                    if (rcse.mRccId == rccId) {
2291                        rcse.mRemoteVolumeObs = rvo;
2292                        break;
2293                    }
2294                }
2295            } catch (ArrayIndexOutOfBoundsException e) {
2296                // not expected to happen, indicates improper concurrent modification
2297                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
2298            }
2299        }
2300    }
2301
2302    /**
2303     * Checks if a remote client is active on the supplied stream type. Update the remote stream
2304     * volume state if found and playing
2305     * @param streamType
2306     * @return false if no remote playing is currently playing
2307     */
2308    protected boolean checkUpdateRemoteStateIfActive(int streamType) {
2309        synchronized(mRCStack) {
2310            // iterating from top of stack as active playback is more likely on entries at the top
2311            try {
2312                for (int index = mRCStack.size()-1; index >= 0; index--) {
2313                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
2314                    if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
2315                            && isPlaystateActive(rcse.mPlaybackState.mState)
2316                            && (rcse.mPlaybackStream == streamType)) {
2317                        if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
2318                                + ", vol =" + rcse.mPlaybackVolume);
2319                        synchronized (mMainRemote) {
2320                            mMainRemote.mRccId = rcse.mRccId;
2321                            mMainRemote.mVolume = rcse.mPlaybackVolume;
2322                            mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax;
2323                            mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling;
2324                            mMainRemoteIsActive = true;
2325                        }
2326                        return true;
2327                    }
2328                }
2329            } catch (ArrayIndexOutOfBoundsException e) {
2330                // not expected to happen, indicates improper concurrent modification
2331                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
2332            }
2333        }
2334        synchronized (mMainRemote) {
2335            mMainRemoteIsActive = false;
2336        }
2337        return false;
2338    }
2339
2340    /**
2341     * Returns true if the given playback state is considered "active", i.e. it describes a state
2342     * where playback is happening, or about to
2343     * @param playState the playback state to evaluate
2344     * @return true if active, false otherwise (inactive or unknown)
2345     */
2346    private static boolean isPlaystateActive(int playState) {
2347        switch (playState) {
2348            case RemoteControlClient.PLAYSTATE_PLAYING:
2349            case RemoteControlClient.PLAYSTATE_BUFFERING:
2350            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
2351            case RemoteControlClient.PLAYSTATE_REWINDING:
2352            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
2353            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
2354                return true;
2355            default:
2356                return false;
2357        }
2358    }
2359
2360    protected void adjustRemoteVolume(int streamType, int direction, int flags) {
2361        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
2362        boolean volFixed = false;
2363        synchronized (mMainRemote) {
2364            if (!mMainRemoteIsActive) {
2365                if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client");
2366                return;
2367            }
2368            rccId = mMainRemote.mRccId;
2369            volFixed = (mMainRemote.mVolumeHandling ==
2370                    RemoteControlClient.PLAYBACK_VOLUME_FIXED);
2371        }
2372        // unlike "local" stream volumes, we can't compute the new volume based on the direction,
2373        // we can only notify the remote that volume needs to be updated, and we'll get an async'
2374        // update through setPlaybackInfoForRcc()
2375        if (!volFixed) {
2376            sendVolumeUpdateToRemote(rccId, direction);
2377        }
2378
2379        // fire up the UI
2380        mVolumeController.postRemoteVolumeChanged(streamType, flags);
2381    }
2382
2383    private void sendVolumeUpdateToRemote(int rccId, int direction) {
2384        if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
2385        if (direction == 0) {
2386            // only handling discrete events
2387            return;
2388        }
2389        IRemoteVolumeObserver rvo = null;
2390        synchronized (mRCStack) {
2391            // The stack traversal order doesn't matter because there is only one stack entry
2392            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
2393            //  start iterating from the top.
2394            try {
2395                for (int index = mRCStack.size()-1; index >= 0; index--) {
2396                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
2397                    //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
2398                    if (rcse.mRccId == rccId) {
2399                        rvo = rcse.mRemoteVolumeObs;
2400                        break;
2401                    }
2402                }
2403            } catch (ArrayIndexOutOfBoundsException e) {
2404                // not expected to happen, indicates improper concurrent modification
2405                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
2406            }
2407        }
2408        if (rvo != null) {
2409            try {
2410                rvo.dispatchRemoteVolumeUpdate(direction, -1);
2411            } catch (RemoteException e) {
2412                Log.e(TAG, "Error dispatching relative volume update", e);
2413            }
2414        }
2415    }
2416
2417    protected int getRemoteStreamMaxVolume() {
2418        synchronized (mMainRemote) {
2419            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
2420                return 0;
2421            }
2422            return mMainRemote.mVolumeMax;
2423        }
2424    }
2425
2426    protected int getRemoteStreamVolume() {
2427        synchronized (mMainRemote) {
2428            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
2429                return 0;
2430            }
2431            return mMainRemote.mVolume;
2432        }
2433    }
2434
2435    protected void setRemoteStreamVolume(int vol) {
2436        if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
2437        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
2438        synchronized (mMainRemote) {
2439            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
2440                return;
2441            }
2442            rccId = mMainRemote.mRccId;
2443        }
2444        IRemoteVolumeObserver rvo = null;
2445        synchronized (mRCStack) {
2446            // The stack traversal order doesn't matter because there is only one stack entry
2447            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
2448            //  start iterating from the top.
2449            try {
2450                for (int index = mRCStack.size()-1; index >= 0; index--) {
2451                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
2452                    //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
2453                    if (rcse.mRccId == rccId) {
2454                        rvo = rcse.mRemoteVolumeObs;
2455                        break;
2456                    }
2457                }
2458            } catch (ArrayIndexOutOfBoundsException e) {
2459                // not expected to happen, indicates improper concurrent modification
2460                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
2461            }
2462        }
2463        if (rvo != null) {
2464            try {
2465                rvo.dispatchRemoteVolumeUpdate(0, vol);
2466            } catch (RemoteException e) {
2467                Log.e(TAG, "Error dispatching absolute volume update", e);
2468            }
2469        }
2470    }
2471
2472    /**
2473     * Call to make AudioService reevaluate whether it's in a mode where remote players should
2474     * have their volume controlled. In this implementation this is only to reset whether
2475     * VolumePanel should display remote volumes
2476     */
2477    private void postReevaluateRemote() {
2478        sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
2479    }
2480
2481    private void onReevaluateRemote() {
2482        if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); }
2483        // is there a registered RemoteControlClient that is handling remote playback
2484        boolean hasRemotePlayback = false;
2485        synchronized (mRCStack) {
2486            // iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack
2487            //   traversal order doesn't matter
2488            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
2489            while(stackIterator.hasNext()) {
2490                RemoteControlStackEntry rcse = stackIterator.next();
2491                if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
2492                    hasRemotePlayback = true;
2493                    break;
2494                }
2495            }
2496        }
2497        synchronized (mMainRemote) {
2498            if (mHasRemotePlayback != hasRemotePlayback) {
2499                mHasRemotePlayback = hasRemotePlayback;
2500                mVolumeController.postRemoteSliderVisibility(hasRemotePlayback);
2501            }
2502        }
2503    }
2504
2505}
2506