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