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