Home | History | Annotate | Download | only in audio
      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 
     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 
    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 
    147         NotificationListenerObserver() {
    148             super(mEventHandler);
    149             mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
    150                     Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this);
    151         }
    152 
    153         @Override
    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      */
    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 
    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 
    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 
    246     private void postReevaluateRemoteControlDisplays() {
    247         sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
    248     }
    249 
    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      */
    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 
    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 {
    360         MediaEventHandler(Looper looper) {
    361             super(looper);
    362         }
    363 
    364         @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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 
    614         AudioFocusDeathHandler(IBinder cb) {
    615             mCb = cb;
    616         }
    617 
    618         public void binderDied() {
    619             synchronized(mAudioFocusLock) {
    620                 Log.w(TAG, "  AudioFocus   audio focus client died");
    621                 removeFocusStackEntryForClient(mCb);
    622             }
    623         }
    624 
    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 
    638     protected void setDuckingInExtPolicyAvailable(boolean available) {
    639         mNotifyFocusOwnerOnDuck = !available;
    640     }
    641 
    642     boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
    643 
    644     private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
    645 
    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 
    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      */
    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      */
    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      */
    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 
    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) */
    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      * */
    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 
    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      */
    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      */
    886     protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
    887         filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
    888     }
    889 
    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      */
    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      */
    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      */
    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 
   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 
   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      */
   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      */
   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 
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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 
   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      */
   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      */
   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      */
   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      */
   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 
   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 
   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 
   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 
   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      */
   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 
   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      */
   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      */
   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      */
   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      */
   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      */
   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
   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      */
   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      */
   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 
   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 
   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 
   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 
   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      */
   2228     protected void postReevaluateRemote() {
   2229         sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
   2230     }
   2231 
   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