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