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