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