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 com.android.server.media;
     18 
     19 import com.android.internal.util.DumpUtils;
     20 import com.android.server.Watchdog;
     21 
     22 import android.annotation.NonNull;
     23 import android.app.ActivityManager;
     24 import android.bluetooth.BluetoothA2dp;
     25 import android.bluetooth.BluetoothDevice;
     26 import android.bluetooth.BluetoothProfile;
     27 import android.content.BroadcastReceiver;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.IntentFilter;
     31 import android.content.pm.PackageManager;
     32 import android.media.AudioPlaybackConfiguration;
     33 import android.media.AudioRoutesInfo;
     34 import android.media.AudioSystem;
     35 import android.media.IAudioRoutesObserver;
     36 import android.media.IAudioService;
     37 import android.media.IMediaRouterClient;
     38 import android.media.IMediaRouterService;
     39 import android.media.MediaRouter;
     40 import android.media.MediaRouterClientState;
     41 import android.media.RemoteDisplayState;
     42 import android.media.RemoteDisplayState.RemoteDisplayInfo;
     43 import android.os.Binder;
     44 import android.os.Handler;
     45 import android.os.IBinder;
     46 import android.os.Looper;
     47 import android.os.Message;
     48 import android.os.RemoteException;
     49 import android.os.ServiceManager;
     50 import android.os.SystemClock;
     51 import android.os.UserHandle;
     52 import android.text.TextUtils;
     53 import android.util.ArrayMap;
     54 import android.util.IntArray;
     55 import android.util.Log;
     56 import android.util.Slog;
     57 import android.util.SparseArray;
     58 import android.util.TimeUtils;
     59 
     60 import java.io.FileDescriptor;
     61 import java.io.PrintWriter;
     62 import java.util.ArrayList;
     63 import java.util.Collections;
     64 import java.util.List;
     65 import java.util.Objects;
     66 
     67 /**
     68  * Provides a mechanism for discovering media routes and manages media playback
     69  * behalf of applications.
     70  * <p>
     71  * Currently supports discovering remote displays via remote display provider
     72  * services that have been registered by applications.
     73  * </p>
     74  */
     75 public final class MediaRouterService extends IMediaRouterService.Stub
     76         implements Watchdog.Monitor {
     77     private static final String TAG = "MediaRouterService";
     78     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     79 
     80     /**
     81      * Timeout in milliseconds for a selected route to transition from a
     82      * disconnected state to a connecting state.  If we don't observe any
     83      * progress within this interval, then we will give up and unselect the route.
     84      */
     85     static final long CONNECTING_TIMEOUT = 5000;
     86 
     87     /**
     88      * Timeout in milliseconds for a selected route to transition from a
     89      * connecting state to a connected state.  If we don't observe any
     90      * progress within this interval, then we will give up and unselect the route.
     91      */
     92     static final long CONNECTED_TIMEOUT = 60000;
     93 
     94     private final Context mContext;
     95 
     96     // State guarded by mLock.
     97     private final Object mLock = new Object();
     98     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
     99     private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
    100     private int mCurrentUserId = -1;
    101     private final IAudioService mAudioService;
    102     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
    103     private final Handler mHandler = new Handler();
    104     private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
    105     private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
    106 
    107     private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver();
    108     BluetoothDevice mActiveBluetoothDevice;
    109     int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER;
    110     boolean mGlobalBluetoothA2dpOn = false;
    111 
    112     public MediaRouterService(Context context) {
    113         mContext = context;
    114         Watchdog.getInstance().addMonitor(this);
    115 
    116         mAudioService = IAudioService.Stub.asInterface(
    117                 ServiceManager.getService(Context.AUDIO_SERVICE));
    118         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
    119         mAudioPlayerStateMonitor.registerListener(
    120                 new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
    121             static final long WAIT_MS = 500;
    122             final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
    123                 @Override
    124                 public void run() {
    125                     restoreBluetoothA2dp();
    126                 }
    127             };
    128 
    129             @Override
    130             public void onAudioPlayerActiveStateChanged(
    131                     @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
    132                 final boolean active = !isRemoved && config.isActive();
    133                 final int pii = config.getPlayerInterfaceId();
    134                 final int uid = config.getClientUid();
    135 
    136                 final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
    137                 // Keep the latest active player and its uid at the end of the queue.
    138                 if (idx >= 0) {
    139                     mActivePlayerMinPriorityQueue.remove(idx);
    140                     mActivePlayerUidMinPriorityQueue.remove(idx);
    141                 }
    142 
    143                 int restoreUid = -1;
    144                 if (active) {
    145                     mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
    146                     mActivePlayerUidMinPriorityQueue.add(uid);
    147                     restoreUid = uid;
    148                 } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
    149                     restoreUid = mActivePlayerUidMinPriorityQueue.get(
    150                             mActivePlayerUidMinPriorityQueue.size() - 1);
    151                 }
    152 
    153                 mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
    154                 if (restoreUid >= 0) {
    155                     restoreRoute(restoreUid);
    156                     if (DEBUG) {
    157                         Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
    158                                 + ", active=" + active + ", restoreUid=" + restoreUid);
    159                     }
    160                 } else {
    161                     mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
    162                     if (DEBUG) {
    163                         Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
    164                                 + ", active=" + active + ", delaying");
    165                     }
    166                 }
    167             }
    168         }, mHandler);
    169         mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
    170 
    171         AudioRoutesInfo audioRoutes = null;
    172         try {
    173             audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
    174                 @Override
    175                 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
    176                     synchronized (mLock) {
    177                         if (newRoutes.mainType != mAudioRouteMainType) {
    178                             if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
    179                                     | AudioRoutesInfo.MAIN_HEADPHONES
    180                                     | AudioRoutesInfo.MAIN_USB)) == 0) {
    181                                 // headset was plugged out.
    182                                 mGlobalBluetoothA2dpOn = (newRoutes.bluetoothName != null
    183                                         || mActiveBluetoothDevice != null);
    184                             } else {
    185                                 // headset was plugged in.
    186                                 mGlobalBluetoothA2dpOn = false;
    187                             }
    188                             mAudioRouteMainType = newRoutes.mainType;
    189                         }
    190                         // The new audio routes info could be delivered with several seconds delay.
    191                         // In order to avoid such delay, Bluetooth device info will be updated
    192                         // via MediaRouterServiceBroadcastReceiver.
    193                     }
    194                 }
    195             });
    196         } catch (RemoteException e) {
    197             Slog.w(TAG, "RemoteException in the audio service.");
    198         }
    199 
    200         IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
    201         context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
    202     }
    203 
    204     public void systemRunning() {
    205         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
    206         mContext.registerReceiver(new BroadcastReceiver() {
    207             @Override
    208             public void onReceive(Context context, Intent intent) {
    209                 if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
    210                     switchUser();
    211                 }
    212             }
    213         }, filter);
    214 
    215         switchUser();
    216     }
    217 
    218     @Override
    219     public void monitor() {
    220         synchronized (mLock) { /* check for deadlock */ }
    221     }
    222 
    223     // Binder call
    224     @Override
    225     public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
    226         if (client == null) {
    227             throw new IllegalArgumentException("client must not be null");
    228         }
    229 
    230         final int uid = Binder.getCallingUid();
    231         if (!validatePackageName(uid, packageName)) {
    232             throw new SecurityException("packageName must match the calling uid");
    233         }
    234 
    235         final int pid = Binder.getCallingPid();
    236         final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
    237                 false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
    238         final boolean trusted = mContext.checkCallingOrSelfPermission(
    239                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
    240                 PackageManager.PERMISSION_GRANTED;
    241         final long token = Binder.clearCallingIdentity();
    242         try {
    243             synchronized (mLock) {
    244                 registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted);
    245             }
    246         } finally {
    247             Binder.restoreCallingIdentity(token);
    248         }
    249     }
    250 
    251     // Binder call
    252     @Override
    253     public void unregisterClient(IMediaRouterClient client) {
    254         if (client == null) {
    255             throw new IllegalArgumentException("client must not be null");
    256         }
    257 
    258         final long token = Binder.clearCallingIdentity();
    259         try {
    260             synchronized (mLock) {
    261                 unregisterClientLocked(client, false);
    262             }
    263         } finally {
    264             Binder.restoreCallingIdentity(token);
    265         }
    266     }
    267 
    268     // Binder call
    269     @Override
    270     public MediaRouterClientState getState(IMediaRouterClient client) {
    271         if (client == null) {
    272             throw new IllegalArgumentException("client must not be null");
    273         }
    274 
    275         final long token = Binder.clearCallingIdentity();
    276         try {
    277             synchronized (mLock) {
    278                 return getStateLocked(client);
    279             }
    280         } finally {
    281             Binder.restoreCallingIdentity(token);
    282         }
    283     }
    284 
    285     // Binder call
    286     @Override
    287     public boolean isPlaybackActive(IMediaRouterClient client) {
    288         if (client == null) {
    289             throw new IllegalArgumentException("client must not be null");
    290         }
    291 
    292         final long token = Binder.clearCallingIdentity();
    293         try {
    294             ClientRecord clientRecord;
    295             synchronized (mLock) {
    296                 clientRecord = mAllClientRecords.get(client.asBinder());
    297             }
    298             if (clientRecord != null) {
    299                 return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
    300             }
    301             return false;
    302         } finally {
    303             Binder.restoreCallingIdentity(token);
    304         }
    305     }
    306 
    307     // Binder call
    308     @Override
    309     public void setDiscoveryRequest(IMediaRouterClient client,
    310             int routeTypes, boolean activeScan) {
    311         if (client == null) {
    312             throw new IllegalArgumentException("client must not be null");
    313         }
    314 
    315         final long token = Binder.clearCallingIdentity();
    316         try {
    317             synchronized (mLock) {
    318                 setDiscoveryRequestLocked(client, routeTypes, activeScan);
    319             }
    320         } finally {
    321             Binder.restoreCallingIdentity(token);
    322         }
    323     }
    324 
    325     // Binder call
    326     // A null routeId means that the client wants to unselect its current route.
    327     // The explicit flag indicates whether the change was explicitly requested by the
    328     // user or the application which may cause changes to propagate out to the rest
    329     // of the system.  Should be false when the change is in response to a new
    330     // selected route or a default selection.
    331     @Override
    332     public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
    333         if (client == null) {
    334             throw new IllegalArgumentException("client must not be null");
    335         }
    336 
    337         final long token = Binder.clearCallingIdentity();
    338         try {
    339             synchronized (mLock) {
    340                 setSelectedRouteLocked(client, routeId, explicit);
    341             }
    342         } finally {
    343             Binder.restoreCallingIdentity(token);
    344         }
    345     }
    346 
    347     // Binder call
    348     @Override
    349     public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
    350         if (client == null) {
    351             throw new IllegalArgumentException("client must not be null");
    352         }
    353         if (routeId == null) {
    354             throw new IllegalArgumentException("routeId must not be null");
    355         }
    356 
    357         final long token = Binder.clearCallingIdentity();
    358         try {
    359             synchronized (mLock) {
    360                 requestSetVolumeLocked(client, routeId, volume);
    361             }
    362         } finally {
    363             Binder.restoreCallingIdentity(token);
    364         }
    365     }
    366 
    367     // Binder call
    368     @Override
    369     public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
    370         if (client == null) {
    371             throw new IllegalArgumentException("client must not be null");
    372         }
    373         if (routeId == null) {
    374             throw new IllegalArgumentException("routeId must not be null");
    375         }
    376 
    377         final long token = Binder.clearCallingIdentity();
    378         try {
    379             synchronized (mLock) {
    380                 requestUpdateVolumeLocked(client, routeId, direction);
    381             }
    382         } finally {
    383             Binder.restoreCallingIdentity(token);
    384         }
    385     }
    386 
    387     // Binder call
    388     @Override
    389     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
    390         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
    391 
    392         pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
    393         pw.println();
    394         pw.println("Global state");
    395         pw.println("  mCurrentUserId=" + mCurrentUserId);
    396 
    397         synchronized (mLock) {
    398             final int count = mUserRecords.size();
    399             for (int i = 0; i < count; i++) {
    400                 UserRecord userRecord = mUserRecords.valueAt(i);
    401                 pw.println();
    402                 userRecord.dump(pw, "");
    403             }
    404         }
    405     }
    406 
    407     void restoreBluetoothA2dp() {
    408         try {
    409             boolean a2dpOn;
    410             BluetoothDevice btDevice;
    411             synchronized (mLock) {
    412                 a2dpOn = mGlobalBluetoothA2dpOn;
    413                 btDevice = mActiveBluetoothDevice;
    414             }
    415             // We don't need to change a2dp status when bluetooth is not connected.
    416             if (btDevice != null) {
    417                 Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
    418                 mAudioService.setBluetoothA2dpOn(a2dpOn);
    419             }
    420         } catch (RemoteException e) {
    421             Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
    422         }
    423     }
    424 
    425     void restoreRoute(int uid) {
    426         ClientRecord clientRecord = null;
    427         synchronized (mLock) {
    428             UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
    429             if (userRecord != null && userRecord.mClientRecords != null) {
    430                 for (ClientRecord cr : userRecord.mClientRecords) {
    431                     if (validatePackageName(uid, cr.mPackageName)) {
    432                         clientRecord = cr;
    433                         break;
    434                     }
    435                 }
    436             }
    437         }
    438         if (clientRecord != null) {
    439             try {
    440                 clientRecord.mClient.onRestoreRoute();
    441             } catch (RemoteException e) {
    442                 Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died.");
    443             }
    444         } else {
    445             restoreBluetoothA2dp();
    446         }
    447     }
    448 
    449     void switchUser() {
    450         synchronized (mLock) {
    451             int userId = ActivityManager.getCurrentUser();
    452             if (mCurrentUserId != userId) {
    453                 final int oldUserId = mCurrentUserId;
    454                 mCurrentUserId = userId; // do this first
    455 
    456                 UserRecord oldUser = mUserRecords.get(oldUserId);
    457                 if (oldUser != null) {
    458                     oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
    459                     disposeUserIfNeededLocked(oldUser); // since no longer current user
    460                 }
    461 
    462                 UserRecord newUser = mUserRecords.get(userId);
    463                 if (newUser != null) {
    464                     newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
    465                 }
    466             }
    467         }
    468     }
    469 
    470     void clientDied(ClientRecord clientRecord) {
    471         synchronized (mLock) {
    472             unregisterClientLocked(clientRecord.mClient, true);
    473         }
    474     }
    475 
    476     private void registerClientLocked(IMediaRouterClient client,
    477             int uid, int pid, String packageName, int userId, boolean trusted) {
    478         final IBinder binder = client.asBinder();
    479         ClientRecord clientRecord = mAllClientRecords.get(binder);
    480         if (clientRecord == null) {
    481             boolean newUser = false;
    482             UserRecord userRecord = mUserRecords.get(userId);
    483             if (userRecord == null) {
    484                 userRecord = new UserRecord(userId);
    485                 newUser = true;
    486             }
    487             clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
    488             try {
    489                 binder.linkToDeath(clientRecord, 0);
    490             } catch (RemoteException ex) {
    491                 throw new RuntimeException("Media router client died prematurely.", ex);
    492             }
    493 
    494             if (newUser) {
    495                 mUserRecords.put(userId, userRecord);
    496                 initializeUserLocked(userRecord);
    497             }
    498 
    499             userRecord.mClientRecords.add(clientRecord);
    500             mAllClientRecords.put(binder, clientRecord);
    501             initializeClientLocked(clientRecord);
    502         }
    503     }
    504 
    505     private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
    506         ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
    507         if (clientRecord != null) {
    508             UserRecord userRecord = clientRecord.mUserRecord;
    509             userRecord.mClientRecords.remove(clientRecord);
    510             disposeClientLocked(clientRecord, died);
    511             disposeUserIfNeededLocked(userRecord); // since client removed from user
    512         }
    513     }
    514 
    515     private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
    516         ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
    517         if (clientRecord != null) {
    518             return clientRecord.getState();
    519         }
    520         return null;
    521     }
    522 
    523     private void setDiscoveryRequestLocked(IMediaRouterClient client,
    524             int routeTypes, boolean activeScan) {
    525         final IBinder binder = client.asBinder();
    526         ClientRecord clientRecord = mAllClientRecords.get(binder);
    527         if (clientRecord != null) {
    528             // Only let the system discover remote display routes for now.
    529             if (!clientRecord.mTrusted) {
    530                 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
    531             }
    532 
    533             if (clientRecord.mRouteTypes != routeTypes
    534                     || clientRecord.mActiveScan != activeScan) {
    535                 if (DEBUG) {
    536                     Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
    537                             + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
    538                 }
    539                 clientRecord.mRouteTypes = routeTypes;
    540                 clientRecord.mActiveScan = activeScan;
    541                 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
    542                         UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
    543             }
    544         }
    545     }
    546 
    547     private void setSelectedRouteLocked(IMediaRouterClient client,
    548             String routeId, boolean explicit) {
    549         ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
    550         if (clientRecord != null) {
    551             final String oldRouteId = clientRecord.mSelectedRouteId;
    552             if (!Objects.equals(routeId, oldRouteId)) {
    553                 if (DEBUG) {
    554                     Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
    555                             + ", oldRouteId=" + oldRouteId
    556                             + ", explicit=" + explicit);
    557                 }
    558 
    559                 clientRecord.mSelectedRouteId = routeId;
    560                 // Only let the system connect to new global routes for now.
    561                 // A similar check exists in the display manager for wifi display.
    562                 if (explicit && clientRecord.mTrusted) {
    563                     if (oldRouteId != null) {
    564                         clientRecord.mUserRecord.mHandler.obtainMessage(
    565                                 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
    566                     }
    567                     if (routeId != null) {
    568                         clientRecord.mUserRecord.mHandler.obtainMessage(
    569                                 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
    570                     }
    571                 }
    572             }
    573         }
    574     }
    575 
    576     private void requestSetVolumeLocked(IMediaRouterClient client,
    577             String routeId, int volume) {
    578         final IBinder binder = client.asBinder();
    579         ClientRecord clientRecord = mAllClientRecords.get(binder);
    580         if (clientRecord != null) {
    581             clientRecord.mUserRecord.mHandler.obtainMessage(
    582                     UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
    583         }
    584     }
    585 
    586     private void requestUpdateVolumeLocked(IMediaRouterClient client,
    587             String routeId, int direction) {
    588         final IBinder binder = client.asBinder();
    589         ClientRecord clientRecord = mAllClientRecords.get(binder);
    590         if (clientRecord != null) {
    591             clientRecord.mUserRecord.mHandler.obtainMessage(
    592                     UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
    593         }
    594     }
    595 
    596     private void initializeUserLocked(UserRecord userRecord) {
    597         if (DEBUG) {
    598             Slog.d(TAG, userRecord + ": Initialized");
    599         }
    600         if (userRecord.mUserId == mCurrentUserId) {
    601             userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
    602         }
    603     }
    604 
    605     private void disposeUserIfNeededLocked(UserRecord userRecord) {
    606         // If there are no records left and the user is no longer current then go ahead
    607         // and purge the user record and all of its associated state.  If the user is current
    608         // then leave it alone since we might be connected to a route or want to query
    609         // the same route information again soon.
    610         if (userRecord.mUserId != mCurrentUserId
    611                 && userRecord.mClientRecords.isEmpty()) {
    612             if (DEBUG) {
    613                 Slog.d(TAG, userRecord + ": Disposed");
    614             }
    615             mUserRecords.remove(userRecord.mUserId);
    616             // Note: User already stopped (by switchUser) so no need to send stop message here.
    617         }
    618     }
    619 
    620     private void initializeClientLocked(ClientRecord clientRecord) {
    621         if (DEBUG) {
    622             Slog.d(TAG, clientRecord + ": Registered");
    623         }
    624     }
    625 
    626     private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
    627         if (DEBUG) {
    628             if (died) {
    629                 Slog.d(TAG, clientRecord + ": Died!");
    630             } else {
    631                 Slog.d(TAG, clientRecord + ": Unregistered");
    632             }
    633         }
    634         if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
    635             clientRecord.mUserRecord.mHandler.sendEmptyMessage(
    636                     UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
    637         }
    638         clientRecord.dispose();
    639     }
    640 
    641     private boolean validatePackageName(int uid, String packageName) {
    642         if (packageName != null) {
    643             String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
    644             if (packageNames != null) {
    645                 for (String n : packageNames) {
    646                     if (n.equals(packageName)) {
    647                         return true;
    648                     }
    649                 }
    650             }
    651         }
    652         return false;
    653     }
    654 
    655     final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver {
    656         @Override
    657         public void onReceive(Context context, Intent intent) {
    658             if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
    659                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    660                 synchronized (mLock) {
    661                     mActiveBluetoothDevice = btDevice;
    662                     mGlobalBluetoothA2dpOn = btDevice != null;
    663                 }
    664             }
    665         }
    666     }
    667 
    668     /**
    669      * Information about a particular client of the media router.
    670      * The contents of this object is guarded by mLock.
    671      */
    672     final class ClientRecord implements DeathRecipient {
    673         public final UserRecord mUserRecord;
    674         public final IMediaRouterClient mClient;
    675         public final int mUid;
    676         public final int mPid;
    677         public final String mPackageName;
    678         public final boolean mTrusted;
    679 
    680         public int mRouteTypes;
    681         public boolean mActiveScan;
    682         public String mSelectedRouteId;
    683 
    684         public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
    685                 int uid, int pid, String packageName, boolean trusted) {
    686             mUserRecord = userRecord;
    687             mClient = client;
    688             mUid = uid;
    689             mPid = pid;
    690             mPackageName = packageName;
    691             mTrusted = trusted;
    692         }
    693 
    694         public void dispose() {
    695             mClient.asBinder().unlinkToDeath(this, 0);
    696         }
    697 
    698         @Override
    699         public void binderDied() {
    700             clientDied(this);
    701         }
    702 
    703         MediaRouterClientState getState() {
    704             return mTrusted ? mUserRecord.mRouterState : null;
    705         }
    706 
    707         public void dump(PrintWriter pw, String prefix) {
    708             pw.println(prefix + this);
    709 
    710             final String indent = prefix + "  ";
    711             pw.println(indent + "mTrusted=" + mTrusted);
    712             pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
    713             pw.println(indent + "mActiveScan=" + mActiveScan);
    714             pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
    715         }
    716 
    717         @Override
    718         public String toString() {
    719             return "Client " + mPackageName + " (pid " + mPid + ")";
    720         }
    721     }
    722 
    723     /**
    724      * Information about a particular user.
    725      * The contents of this object is guarded by mLock.
    726      */
    727     final class UserRecord {
    728         public final int mUserId;
    729         public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
    730         public final UserHandler mHandler;
    731         public MediaRouterClientState mRouterState;
    732 
    733         public UserRecord(int userId) {
    734             mUserId = userId;
    735             mHandler = new UserHandler(MediaRouterService.this, this);
    736         }
    737 
    738         public void dump(final PrintWriter pw, String prefix) {
    739             pw.println(prefix + this);
    740 
    741             final String indent = prefix + "  ";
    742             final int clientCount = mClientRecords.size();
    743             if (clientCount != 0) {
    744                 for (int i = 0; i < clientCount; i++) {
    745                     mClientRecords.get(i).dump(pw, indent);
    746                 }
    747             } else {
    748                 pw.println(indent + "<no clients>");
    749             }
    750 
    751             pw.println(indent + "State");
    752             pw.println(indent + "mRouterState=" + mRouterState);
    753 
    754             if (!mHandler.runWithScissors(new Runnable() {
    755                 @Override
    756                 public void run() {
    757                     mHandler.dump(pw, indent);
    758                 }
    759             }, 1000)) {
    760                 pw.println(indent + "<could not dump handler state>");
    761             }
    762          }
    763 
    764         @Override
    765         public String toString() {
    766             return "User " + mUserId;
    767         }
    768     }
    769 
    770     /**
    771      * Media router handler
    772      * <p>
    773      * Since remote display providers are designed to be single-threaded by nature,
    774      * this class encapsulates all of the associated functionality and exports state
    775      * to the service as it evolves.
    776      * </p><p>
    777      * This class is currently hardcoded to work with remote display providers but
    778      * it is intended to be eventually extended to support more general route providers
    779      * similar to the support library media router.
    780      * </p>
    781      */
    782     static final class UserHandler extends Handler
    783             implements RemoteDisplayProviderWatcher.Callback,
    784             RemoteDisplayProviderProxy.Callback {
    785         public static final int MSG_START = 1;
    786         public static final int MSG_STOP = 2;
    787         public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
    788         public static final int MSG_SELECT_ROUTE = 4;
    789         public static final int MSG_UNSELECT_ROUTE = 5;
    790         public static final int MSG_REQUEST_SET_VOLUME = 6;
    791         public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
    792         private static final int MSG_UPDATE_CLIENT_STATE = 8;
    793         private static final int MSG_CONNECTION_TIMED_OUT = 9;
    794 
    795         private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
    796         private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
    797         private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
    798         private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
    799 
    800         // The relative order of these constants is important and expresses progress
    801         // through the process of connecting to a route.
    802         private static final int PHASE_NOT_AVAILABLE = -1;
    803         private static final int PHASE_NOT_CONNECTED = 0;
    804         private static final int PHASE_CONNECTING = 1;
    805         private static final int PHASE_CONNECTED = 2;
    806 
    807         private final MediaRouterService mService;
    808         private final UserRecord mUserRecord;
    809         private final RemoteDisplayProviderWatcher mWatcher;
    810         private final ArrayList<ProviderRecord> mProviderRecords =
    811                 new ArrayList<ProviderRecord>();
    812         private final ArrayList<IMediaRouterClient> mTempClients =
    813                 new ArrayList<IMediaRouterClient>();
    814 
    815         private boolean mRunning;
    816         private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
    817         private RouteRecord mSelectedRouteRecord;
    818         private int mConnectionPhase = PHASE_NOT_AVAILABLE;
    819         private int mConnectionTimeoutReason;
    820         private long mConnectionTimeoutStartTime;
    821         private boolean mClientStateUpdateScheduled;
    822 
    823         public UserHandler(MediaRouterService service, UserRecord userRecord) {
    824             super(Looper.getMainLooper(), null, true);
    825             mService = service;
    826             mUserRecord = userRecord;
    827             mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
    828                     this, mUserRecord.mUserId);
    829         }
    830 
    831         @Override
    832         public void handleMessage(Message msg) {
    833             switch (msg.what) {
    834                 case MSG_START: {
    835                     start();
    836                     break;
    837                 }
    838                 case MSG_STOP: {
    839                     stop();
    840                     break;
    841                 }
    842                 case MSG_UPDATE_DISCOVERY_REQUEST: {
    843                     updateDiscoveryRequest();
    844                     break;
    845                 }
    846                 case MSG_SELECT_ROUTE: {
    847                     selectRoute((String)msg.obj);
    848                     break;
    849                 }
    850                 case MSG_UNSELECT_ROUTE: {
    851                     unselectRoute((String)msg.obj);
    852                     break;
    853                 }
    854                 case MSG_REQUEST_SET_VOLUME: {
    855                     requestSetVolume((String)msg.obj, msg.arg1);
    856                     break;
    857                 }
    858                 case MSG_REQUEST_UPDATE_VOLUME: {
    859                     requestUpdateVolume((String)msg.obj, msg.arg1);
    860                     break;
    861                 }
    862                 case MSG_UPDATE_CLIENT_STATE: {
    863                     updateClientState();
    864                     break;
    865                 }
    866                 case MSG_CONNECTION_TIMED_OUT: {
    867                     connectionTimedOut();
    868                     break;
    869                 }
    870             }
    871         }
    872 
    873         public void dump(PrintWriter pw, String prefix) {
    874             pw.println(prefix + "Handler");
    875 
    876             final String indent = prefix + "  ";
    877             pw.println(indent + "mRunning=" + mRunning);
    878             pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
    879             pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord);
    880             pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
    881             pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
    882             pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
    883                     TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
    884 
    885             mWatcher.dump(pw, prefix);
    886 
    887             final int providerCount = mProviderRecords.size();
    888             if (providerCount != 0) {
    889                 for (int i = 0; i < providerCount; i++) {
    890                     mProviderRecords.get(i).dump(pw, prefix);
    891                 }
    892             } else {
    893                 pw.println(indent + "<no providers>");
    894             }
    895         }
    896 
    897         private void start() {
    898             if (!mRunning) {
    899                 mRunning = true;
    900                 mWatcher.start(); // also starts all providers
    901             }
    902         }
    903 
    904         private void stop() {
    905             if (mRunning) {
    906                 mRunning = false;
    907                 unselectSelectedRoute();
    908                 mWatcher.stop(); // also stops all providers
    909             }
    910         }
    911 
    912         private void updateDiscoveryRequest() {
    913             int routeTypes = 0;
    914             boolean activeScan = false;
    915             synchronized (mService.mLock) {
    916                 final int count = mUserRecord.mClientRecords.size();
    917                 for (int i = 0; i < count; i++) {
    918                     ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
    919                     routeTypes |= clientRecord.mRouteTypes;
    920                     activeScan |= clientRecord.mActiveScan;
    921                 }
    922             }
    923 
    924             final int newDiscoveryMode;
    925             if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
    926                 if (activeScan) {
    927                     newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
    928                 } else {
    929                     newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
    930                 }
    931             } else {
    932                 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
    933             }
    934 
    935             if (mDiscoveryMode != newDiscoveryMode) {
    936                 mDiscoveryMode = newDiscoveryMode;
    937                 final int count = mProviderRecords.size();
    938                 for (int i = 0; i < count; i++) {
    939                     mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
    940                 }
    941             }
    942         }
    943 
    944         private void selectRoute(String routeId) {
    945             if (routeId != null
    946                     && (mSelectedRouteRecord == null
    947                             || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) {
    948                 RouteRecord routeRecord = findRouteRecord(routeId);
    949                 if (routeRecord != null) {
    950                     unselectSelectedRoute();
    951 
    952                     Slog.i(TAG, "Selected route:" + routeRecord);
    953                     mSelectedRouteRecord = routeRecord;
    954                     checkSelectedRouteState();
    955                     routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
    956 
    957                     scheduleUpdateClientState();
    958                 }
    959             }
    960         }
    961 
    962         private void unselectRoute(String routeId) {
    963             if (routeId != null
    964                     && mSelectedRouteRecord != null
    965                     && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
    966                 unselectSelectedRoute();
    967             }
    968         }
    969 
    970         private void unselectSelectedRoute() {
    971             if (mSelectedRouteRecord != null) {
    972                 Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord);
    973                 mSelectedRouteRecord.getProvider().setSelectedDisplay(null);
    974                 mSelectedRouteRecord = null;
    975                 checkSelectedRouteState();
    976 
    977                 scheduleUpdateClientState();
    978             }
    979         }
    980 
    981         private void requestSetVolume(String routeId, int volume) {
    982             if (mSelectedRouteRecord != null
    983                     && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
    984                 mSelectedRouteRecord.getProvider().setDisplayVolume(volume);
    985             }
    986         }
    987 
    988         private void requestUpdateVolume(String routeId, int direction) {
    989             if (mSelectedRouteRecord != null
    990                     && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
    991                 mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
    992             }
    993         }
    994 
    995         @Override
    996         public void addProvider(RemoteDisplayProviderProxy provider) {
    997             provider.setCallback(this);
    998             provider.setDiscoveryMode(mDiscoveryMode);
    999             provider.setSelectedDisplay(null); // just to be safe
   1000 
   1001             ProviderRecord providerRecord = new ProviderRecord(provider);
   1002             mProviderRecords.add(providerRecord);
   1003             providerRecord.updateDescriptor(provider.getDisplayState());
   1004 
   1005             scheduleUpdateClientState();
   1006         }
   1007 
   1008         @Override
   1009         public void removeProvider(RemoteDisplayProviderProxy provider) {
   1010             int index = findProviderRecord(provider);
   1011             if (index >= 0) {
   1012                 ProviderRecord providerRecord = mProviderRecords.remove(index);
   1013                 providerRecord.updateDescriptor(null); // mark routes invalid
   1014                 provider.setCallback(null);
   1015                 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
   1016 
   1017                 checkSelectedRouteState();
   1018                 scheduleUpdateClientState();
   1019             }
   1020         }
   1021 
   1022         @Override
   1023         public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
   1024                 RemoteDisplayState state) {
   1025             updateProvider(provider, state);
   1026         }
   1027 
   1028         private void updateProvider(RemoteDisplayProviderProxy provider,
   1029                 RemoteDisplayState state) {
   1030             int index = findProviderRecord(provider);
   1031             if (index >= 0) {
   1032                 ProviderRecord providerRecord = mProviderRecords.get(index);
   1033                 if (providerRecord.updateDescriptor(state)) {
   1034                     checkSelectedRouteState();
   1035                     scheduleUpdateClientState();
   1036                 }
   1037             }
   1038         }
   1039 
   1040         /**
   1041          * This function is called whenever the state of the selected route may have changed.
   1042          * It checks the state and updates timeouts or unselects the route as appropriate.
   1043          */
   1044         private void checkSelectedRouteState() {
   1045             // Unschedule timeouts when the route is unselected.
   1046             if (mSelectedRouteRecord == null) {
   1047                 mConnectionPhase = PHASE_NOT_AVAILABLE;
   1048                 updateConnectionTimeout(0);
   1049                 return;
   1050             }
   1051 
   1052             // Ensure that the route is still present and enabled.
   1053             if (!mSelectedRouteRecord.isValid()
   1054                     || !mSelectedRouteRecord.isEnabled()) {
   1055                 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
   1056                 return;
   1057             }
   1058 
   1059             // Make sure we haven't lost our connection.
   1060             final int oldPhase = mConnectionPhase;
   1061             mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus());
   1062             if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
   1063                 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
   1064                 return;
   1065             }
   1066 
   1067             // Check the route status.
   1068             switch (mConnectionPhase) {
   1069                 case PHASE_CONNECTED:
   1070                     if (oldPhase != PHASE_CONNECTED) {
   1071                         Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord);
   1072                     }
   1073                     updateConnectionTimeout(0);
   1074                     break;
   1075                 case PHASE_CONNECTING:
   1076                     if (oldPhase != PHASE_CONNECTING) {
   1077                         Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord);
   1078                     }
   1079                     updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
   1080                     break;
   1081                 case PHASE_NOT_CONNECTED:
   1082                     updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
   1083                     break;
   1084                 case PHASE_NOT_AVAILABLE:
   1085                 default:
   1086                     updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
   1087                     break;
   1088             }
   1089         }
   1090 
   1091         private void updateConnectionTimeout(int reason) {
   1092             if (reason != mConnectionTimeoutReason) {
   1093                 if (mConnectionTimeoutReason != 0) {
   1094                     removeMessages(MSG_CONNECTION_TIMED_OUT);
   1095                 }
   1096                 mConnectionTimeoutReason = reason;
   1097                 mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
   1098                 switch (reason) {
   1099                     case TIMEOUT_REASON_NOT_AVAILABLE:
   1100                     case TIMEOUT_REASON_CONNECTION_LOST:
   1101                         // Route became unavailable or connection lost.
   1102                         // Unselect it immediately.
   1103                         sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
   1104                         break;
   1105                     case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
   1106                         // Waiting for route to start connecting.
   1107                         sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
   1108                         break;
   1109                     case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
   1110                         // Waiting for route to complete connection.
   1111                         sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
   1112                         break;
   1113                 }
   1114             }
   1115         }
   1116 
   1117         private void connectionTimedOut() {
   1118             if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) {
   1119                 // Shouldn't get here.  There must be a bug somewhere.
   1120                 Log.wtf(TAG, "Handled connection timeout for no reason.");
   1121                 return;
   1122             }
   1123 
   1124             switch (mConnectionTimeoutReason) {
   1125                 case TIMEOUT_REASON_NOT_AVAILABLE:
   1126                     Slog.i(TAG, "Selected route no longer available: "
   1127                             + mSelectedRouteRecord);
   1128                     break;
   1129                 case TIMEOUT_REASON_CONNECTION_LOST:
   1130                     Slog.i(TAG, "Selected route connection lost: "
   1131                             + mSelectedRouteRecord);
   1132                     break;
   1133                 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
   1134                     Slog.i(TAG, "Selected route timed out while waiting for "
   1135                             + "connection attempt to begin after "
   1136                             + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
   1137                             + " ms: " + mSelectedRouteRecord);
   1138                     break;
   1139                 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
   1140                     Slog.i(TAG, "Selected route timed out while connecting after "
   1141                             + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
   1142                             + " ms: " + mSelectedRouteRecord);
   1143                     break;
   1144             }
   1145             mConnectionTimeoutReason = 0;
   1146 
   1147             unselectSelectedRoute();
   1148         }
   1149 
   1150         private void scheduleUpdateClientState() {
   1151             if (!mClientStateUpdateScheduled) {
   1152                 mClientStateUpdateScheduled = true;
   1153                 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
   1154             }
   1155         }
   1156 
   1157         private void updateClientState() {
   1158             mClientStateUpdateScheduled = false;
   1159 
   1160             // Build a new client state for trusted clients.
   1161             MediaRouterClientState routerState = new MediaRouterClientState();
   1162             final int providerCount = mProviderRecords.size();
   1163             for (int i = 0; i < providerCount; i++) {
   1164                 mProviderRecords.get(i).appendClientState(routerState);
   1165             }
   1166 
   1167             try {
   1168                 synchronized (mService.mLock) {
   1169                     // Update the UserRecord.
   1170                     mUserRecord.mRouterState = routerState;
   1171 
   1172                     // Collect all clients.
   1173                     final int count = mUserRecord.mClientRecords.size();
   1174                     for (int i = 0; i < count; i++) {
   1175                         mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
   1176                     }
   1177                 }
   1178 
   1179                 // Notify all clients (outside of the lock).
   1180                 final int count = mTempClients.size();
   1181                 for (int i = 0; i < count; i++) {
   1182                     try {
   1183                         mTempClients.get(i).onStateChanged();
   1184                     } catch (RemoteException ex) {
   1185                         Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
   1186                     }
   1187                 }
   1188             } finally {
   1189                 // Clear the list in preparation for the next time.
   1190                 mTempClients.clear();
   1191             }
   1192         }
   1193 
   1194         private int findProviderRecord(RemoteDisplayProviderProxy provider) {
   1195             final int count = mProviderRecords.size();
   1196             for (int i = 0; i < count; i++) {
   1197                 ProviderRecord record = mProviderRecords.get(i);
   1198                 if (record.getProvider() == provider) {
   1199                     return i;
   1200                 }
   1201             }
   1202             return -1;
   1203         }
   1204 
   1205         private RouteRecord findRouteRecord(String uniqueId) {
   1206             final int count = mProviderRecords.size();
   1207             for (int i = 0; i < count; i++) {
   1208                 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
   1209                 if (record != null) {
   1210                     return record;
   1211                 }
   1212             }
   1213             return null;
   1214         }
   1215 
   1216         private static int getConnectionPhase(int status) {
   1217             switch (status) {
   1218                 case MediaRouter.RouteInfo.STATUS_NONE:
   1219                 case MediaRouter.RouteInfo.STATUS_CONNECTED:
   1220                     return PHASE_CONNECTED;
   1221                 case MediaRouter.RouteInfo.STATUS_CONNECTING:
   1222                     return PHASE_CONNECTING;
   1223                 case MediaRouter.RouteInfo.STATUS_SCANNING:
   1224                 case MediaRouter.RouteInfo.STATUS_AVAILABLE:
   1225                     return PHASE_NOT_CONNECTED;
   1226                 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
   1227                 case MediaRouter.RouteInfo.STATUS_IN_USE:
   1228                 default:
   1229                     return PHASE_NOT_AVAILABLE;
   1230             }
   1231         }
   1232 
   1233         static final class ProviderRecord {
   1234             private final RemoteDisplayProviderProxy mProvider;
   1235             private final String mUniquePrefix;
   1236             private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
   1237             private RemoteDisplayState mDescriptor;
   1238 
   1239             public ProviderRecord(RemoteDisplayProviderProxy provider) {
   1240                 mProvider = provider;
   1241                 mUniquePrefix = provider.getFlattenedComponentName() + ":";
   1242             }
   1243 
   1244             public RemoteDisplayProviderProxy getProvider() {
   1245                 return mProvider;
   1246             }
   1247 
   1248             public String getUniquePrefix() {
   1249                 return mUniquePrefix;
   1250             }
   1251 
   1252             public boolean updateDescriptor(RemoteDisplayState descriptor) {
   1253                 boolean changed = false;
   1254                 if (mDescriptor != descriptor) {
   1255                     mDescriptor = descriptor;
   1256 
   1257                     // Update all existing routes and reorder them to match
   1258                     // the order of their descriptors.
   1259                     int targetIndex = 0;
   1260                     if (descriptor != null) {
   1261                         if (descriptor.isValid()) {
   1262                             final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
   1263                             final int routeCount = routeDescriptors.size();
   1264                             for (int i = 0; i < routeCount; i++) {
   1265                                 final RemoteDisplayInfo routeDescriptor =
   1266                                         routeDescriptors.get(i);
   1267                                 final String descriptorId = routeDescriptor.id;
   1268                                 final int sourceIndex = findRouteByDescriptorId(descriptorId);
   1269                                 if (sourceIndex < 0) {
   1270                                     // Add the route to the provider.
   1271                                     String uniqueId = assignRouteUniqueId(descriptorId);
   1272                                     RouteRecord route =
   1273                                             new RouteRecord(this, descriptorId, uniqueId);
   1274                                     mRoutes.add(targetIndex++, route);
   1275                                     route.updateDescriptor(routeDescriptor);
   1276                                     changed = true;
   1277                                 } else if (sourceIndex < targetIndex) {
   1278                                     // Ignore route with duplicate id.
   1279                                     Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
   1280                                             + routeDescriptor);
   1281                                 } else {
   1282                                     // Reorder existing route within the list.
   1283                                     RouteRecord route = mRoutes.get(sourceIndex);
   1284                                     Collections.swap(mRoutes, sourceIndex, targetIndex++);
   1285                                     changed |= route.updateDescriptor(routeDescriptor);
   1286                                 }
   1287                             }
   1288                         } else {
   1289                             Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
   1290                                     + mProvider.getFlattenedComponentName());
   1291                         }
   1292                     }
   1293 
   1294                     // Dispose all remaining routes that do not have matching descriptors.
   1295                     for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
   1296                         RouteRecord route = mRoutes.remove(i);
   1297                         route.updateDescriptor(null); // mark route invalid
   1298                         changed = true;
   1299                     }
   1300                 }
   1301                 return changed;
   1302             }
   1303 
   1304             public void appendClientState(MediaRouterClientState state) {
   1305                 final int routeCount = mRoutes.size();
   1306                 for (int i = 0; i < routeCount; i++) {
   1307                     state.routes.add(mRoutes.get(i).getInfo());
   1308                 }
   1309             }
   1310 
   1311             public RouteRecord findRouteByUniqueId(String uniqueId) {
   1312                 final int routeCount = mRoutes.size();
   1313                 for (int i = 0; i < routeCount; i++) {
   1314                     RouteRecord route = mRoutes.get(i);
   1315                     if (route.getUniqueId().equals(uniqueId)) {
   1316                         return route;
   1317                     }
   1318                 }
   1319                 return null;
   1320             }
   1321 
   1322             private int findRouteByDescriptorId(String descriptorId) {
   1323                 final int routeCount = mRoutes.size();
   1324                 for (int i = 0; i < routeCount; i++) {
   1325                     RouteRecord route = mRoutes.get(i);
   1326                     if (route.getDescriptorId().equals(descriptorId)) {
   1327                         return i;
   1328                     }
   1329                 }
   1330                 return -1;
   1331             }
   1332 
   1333             public void dump(PrintWriter pw, String prefix) {
   1334                 pw.println(prefix + this);
   1335 
   1336                 final String indent = prefix + "  ";
   1337                 mProvider.dump(pw, indent);
   1338 
   1339                 final int routeCount = mRoutes.size();
   1340                 if (routeCount != 0) {
   1341                     for (int i = 0; i < routeCount; i++) {
   1342                         mRoutes.get(i).dump(pw, indent);
   1343                     }
   1344                 } else {
   1345                     pw.println(indent + "<no routes>");
   1346                 }
   1347             }
   1348 
   1349             @Override
   1350             public String toString() {
   1351                 return "Provider " + mProvider.getFlattenedComponentName();
   1352             }
   1353 
   1354             private String assignRouteUniqueId(String descriptorId) {
   1355                 return mUniquePrefix + descriptorId;
   1356             }
   1357         }
   1358 
   1359         static final class RouteRecord {
   1360             private final ProviderRecord mProviderRecord;
   1361             private final String mDescriptorId;
   1362             private final MediaRouterClientState.RouteInfo mMutableInfo;
   1363             private MediaRouterClientState.RouteInfo mImmutableInfo;
   1364             private RemoteDisplayInfo mDescriptor;
   1365 
   1366             public RouteRecord(ProviderRecord providerRecord,
   1367                     String descriptorId, String uniqueId) {
   1368                 mProviderRecord = providerRecord;
   1369                 mDescriptorId = descriptorId;
   1370                 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
   1371             }
   1372 
   1373             public RemoteDisplayProviderProxy getProvider() {
   1374                 return mProviderRecord.getProvider();
   1375             }
   1376 
   1377             public ProviderRecord getProviderRecord() {
   1378                 return mProviderRecord;
   1379             }
   1380 
   1381             public String getDescriptorId() {
   1382                 return mDescriptorId;
   1383             }
   1384 
   1385             public String getUniqueId() {
   1386                 return mMutableInfo.id;
   1387             }
   1388 
   1389             public MediaRouterClientState.RouteInfo getInfo() {
   1390                 if (mImmutableInfo == null) {
   1391                     mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
   1392                 }
   1393                 return mImmutableInfo;
   1394             }
   1395 
   1396             public boolean isValid() {
   1397                 return mDescriptor != null;
   1398             }
   1399 
   1400             public boolean isEnabled() {
   1401                 return mMutableInfo.enabled;
   1402             }
   1403 
   1404             public int getStatus() {
   1405                 return mMutableInfo.statusCode;
   1406             }
   1407 
   1408             public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
   1409                 boolean changed = false;
   1410                 if (mDescriptor != descriptor) {
   1411                     mDescriptor = descriptor;
   1412                     if (descriptor != null) {
   1413                         final String name = computeName(descriptor);
   1414                         if (!Objects.equals(mMutableInfo.name, name)) {
   1415                             mMutableInfo.name = name;
   1416                             changed = true;
   1417                         }
   1418                         final String description = computeDescription(descriptor);
   1419                         if (!Objects.equals(mMutableInfo.description, description)) {
   1420                             mMutableInfo.description = description;
   1421                             changed = true;
   1422                         }
   1423                         final int supportedTypes = computeSupportedTypes(descriptor);
   1424                         if (mMutableInfo.supportedTypes != supportedTypes) {
   1425                             mMutableInfo.supportedTypes = supportedTypes;
   1426                             changed = true;
   1427                         }
   1428                         final boolean enabled = computeEnabled(descriptor);
   1429                         if (mMutableInfo.enabled != enabled) {
   1430                             mMutableInfo.enabled = enabled;
   1431                             changed = true;
   1432                         }
   1433                         final int statusCode = computeStatusCode(descriptor);
   1434                         if (mMutableInfo.statusCode != statusCode) {
   1435                             mMutableInfo.statusCode = statusCode;
   1436                             changed = true;
   1437                         }
   1438                         final int playbackType = computePlaybackType(descriptor);
   1439                         if (mMutableInfo.playbackType != playbackType) {
   1440                             mMutableInfo.playbackType = playbackType;
   1441                             changed = true;
   1442                         }
   1443                         final int playbackStream = computePlaybackStream(descriptor);
   1444                         if (mMutableInfo.playbackStream != playbackStream) {
   1445                             mMutableInfo.playbackStream = playbackStream;
   1446                             changed = true;
   1447                         }
   1448                         final int volume = computeVolume(descriptor);
   1449                         if (mMutableInfo.volume != volume) {
   1450                             mMutableInfo.volume = volume;
   1451                             changed = true;
   1452                         }
   1453                         final int volumeMax = computeVolumeMax(descriptor);
   1454                         if (mMutableInfo.volumeMax != volumeMax) {
   1455                             mMutableInfo.volumeMax = volumeMax;
   1456                             changed = true;
   1457                         }
   1458                         final int volumeHandling = computeVolumeHandling(descriptor);
   1459                         if (mMutableInfo.volumeHandling != volumeHandling) {
   1460                             mMutableInfo.volumeHandling = volumeHandling;
   1461                             changed = true;
   1462                         }
   1463                         final int presentationDisplayId = computePresentationDisplayId(descriptor);
   1464                         if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
   1465                             mMutableInfo.presentationDisplayId = presentationDisplayId;
   1466                             changed = true;
   1467                         }
   1468                     }
   1469                 }
   1470                 if (changed) {
   1471                     mImmutableInfo = null;
   1472                 }
   1473                 return changed;
   1474             }
   1475 
   1476             public void dump(PrintWriter pw, String prefix) {
   1477                 pw.println(prefix + this);
   1478 
   1479                 final String indent = prefix + "  ";
   1480                 pw.println(indent + "mMutableInfo=" + mMutableInfo);
   1481                 pw.println(indent + "mDescriptorId=" + mDescriptorId);
   1482                 pw.println(indent + "mDescriptor=" + mDescriptor);
   1483             }
   1484 
   1485             @Override
   1486             public String toString() {
   1487                 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
   1488             }
   1489 
   1490             private static String computeName(RemoteDisplayInfo descriptor) {
   1491                 // Note that isValid() already ensures the name is non-empty.
   1492                 return descriptor.name;
   1493             }
   1494 
   1495             private static String computeDescription(RemoteDisplayInfo descriptor) {
   1496                 final String description = descriptor.description;
   1497                 return TextUtils.isEmpty(description) ? null : description;
   1498             }
   1499 
   1500             private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
   1501                 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
   1502                         | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
   1503                         | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
   1504             }
   1505 
   1506             private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
   1507                 switch (descriptor.status) {
   1508                     case RemoteDisplayInfo.STATUS_CONNECTED:
   1509                     case RemoteDisplayInfo.STATUS_CONNECTING:
   1510                     case RemoteDisplayInfo.STATUS_AVAILABLE:
   1511                         return true;
   1512                     default:
   1513                         return false;
   1514                 }
   1515             }
   1516 
   1517             private static int computeStatusCode(RemoteDisplayInfo descriptor) {
   1518                 switch (descriptor.status) {
   1519                     case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
   1520                         return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
   1521                     case RemoteDisplayInfo.STATUS_AVAILABLE:
   1522                         return MediaRouter.RouteInfo.STATUS_AVAILABLE;
   1523                     case RemoteDisplayInfo.STATUS_IN_USE:
   1524                         return MediaRouter.RouteInfo.STATUS_IN_USE;
   1525                     case RemoteDisplayInfo.STATUS_CONNECTING:
   1526                         return MediaRouter.RouteInfo.STATUS_CONNECTING;
   1527                     case RemoteDisplayInfo.STATUS_CONNECTED:
   1528                         return MediaRouter.RouteInfo.STATUS_CONNECTED;
   1529                     default:
   1530                         return MediaRouter.RouteInfo.STATUS_NONE;
   1531                 }
   1532             }
   1533 
   1534             private static int computePlaybackType(RemoteDisplayInfo descriptor) {
   1535                 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
   1536             }
   1537 
   1538             private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
   1539                 return AudioSystem.STREAM_MUSIC;
   1540             }
   1541 
   1542             private static int computeVolume(RemoteDisplayInfo descriptor) {
   1543                 final int volume = descriptor.volume;
   1544                 final int volumeMax = descriptor.volumeMax;
   1545                 if (volume < 0) {
   1546                     return 0;
   1547                 } else if (volume > volumeMax) {
   1548                     return volumeMax;
   1549                 }
   1550                 return volume;
   1551             }
   1552 
   1553             private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
   1554                 final int volumeMax = descriptor.volumeMax;
   1555                 return volumeMax > 0 ? volumeMax : 0;
   1556             }
   1557 
   1558             private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
   1559                 final int volumeHandling = descriptor.volumeHandling;
   1560                 switch (volumeHandling) {
   1561                     case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
   1562                         return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
   1563                     case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
   1564                     default:
   1565                         return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
   1566                 }
   1567             }
   1568 
   1569             private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
   1570                 // The MediaRouter class validates that the id corresponds to an extant
   1571                 // presentation display.  So all we do here is canonicalize the null case.
   1572                 final int displayId = descriptor.presentationDisplayId;
   1573                 return displayId < 0 ? -1 : displayId;
   1574             }
   1575         }
   1576     }
   1577 }
   1578