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