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