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