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