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