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