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