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