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