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