Home | History | Annotate | Download | only in policy
      1 /*
      2  * Copyright (C) 2014 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.systemui.statusbar.policy;
     18 
     19 import android.content.Context;
     20 import android.content.pm.ApplicationInfo;
     21 import android.content.pm.PackageManager;
     22 import android.content.pm.PackageManager.NameNotFoundException;
     23 import android.media.MediaRouter;
     24 import android.media.MediaRouter.RouteInfo;
     25 import android.media.projection.MediaProjectionInfo;
     26 import android.media.projection.MediaProjectionManager;
     27 import android.os.Handler;
     28 import android.text.TextUtils;
     29 import android.util.ArrayMap;
     30 import android.util.ArraySet;
     31 import android.util.Log;
     32 
     33 import com.android.systemui.R;
     34 
     35 import java.io.FileDescriptor;
     36 import java.io.PrintWriter;
     37 import java.util.ArrayList;
     38 import java.util.Objects;
     39 import java.util.Set;
     40 import java.util.UUID;
     41 
     42 import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
     43 
     44 /** Platform implementation of the cast controller. **/
     45 public class CastControllerImpl implements CastController {
     46     private static final String TAG = "CastController";
     47     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     48 
     49     private final Context mContext;
     50     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     51     private final MediaRouter mMediaRouter;
     52     private final ArrayMap<String, RouteInfo> mRoutes = new ArrayMap<>();
     53     private final Object mDiscoveringLock = new Object();
     54     private final MediaProjectionManager mProjectionManager;
     55     private final Object mProjectionLock = new Object();
     56 
     57     private boolean mDiscovering;
     58     private boolean mCallbackRegistered;
     59     private MediaProjectionInfo mProjection;
     60 
     61     public CastControllerImpl(Context context) {
     62         mContext = context;
     63         mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
     64         mProjectionManager = (MediaProjectionManager)
     65                 context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
     66         mProjection = mProjectionManager.getActiveProjectionInfo();
     67         mProjectionManager.addCallback(mProjectionCallback, new Handler());
     68         if (DEBUG) Log.d(TAG, "new CastController()");
     69     }
     70 
     71     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     72         pw.println("CastController state:");
     73         pw.print("  mDiscovering="); pw.println(mDiscovering);
     74         pw.print("  mCallbackRegistered="); pw.println(mCallbackRegistered);
     75         pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
     76         pw.print("  mRoutes.size="); pw.println(mRoutes.size());
     77         for (int i = 0; i < mRoutes.size(); i++) {
     78             final RouteInfo route = mRoutes.valueAt(i);
     79             pw.print("    "); pw.println(routeToString(route));
     80         }
     81         pw.print("  mProjection="); pw.println(mProjection);
     82     }
     83 
     84     @Override
     85     public void addCallback(Callback callback) {
     86         mCallbacks.add(callback);
     87         fireOnCastDevicesChanged(callback);
     88         synchronized (mDiscoveringLock) {
     89             handleDiscoveryChangeLocked();
     90         }
     91     }
     92 
     93     @Override
     94     public void removeCallback(Callback callback) {
     95         mCallbacks.remove(callback);
     96         synchronized (mDiscoveringLock) {
     97             handleDiscoveryChangeLocked();
     98         }
     99     }
    100 
    101     @Override
    102     public void setDiscovering(boolean request) {
    103         synchronized (mDiscoveringLock) {
    104             if (mDiscovering == request) return;
    105             mDiscovering = request;
    106             if (DEBUG) Log.d(TAG, "setDiscovering: " + request);
    107             handleDiscoveryChangeLocked();
    108         }
    109     }
    110 
    111     private void handleDiscoveryChangeLocked() {
    112         if (mCallbackRegistered) {
    113             mMediaRouter.removeCallback(mMediaCallback);
    114             mCallbackRegistered = false;
    115         }
    116         if (mDiscovering) {
    117             mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
    118                     MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
    119             mCallbackRegistered = true;
    120         } else if (mCallbacks.size() != 0) {
    121             mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
    122                     MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
    123             mCallbackRegistered = true;
    124         }
    125     }
    126 
    127     @Override
    128     public void setCurrentUserId(int currentUserId) {
    129         mMediaRouter.rebindAsUser(currentUserId);
    130     }
    131 
    132     @Override
    133     public Set<CastDevice> getCastDevices() {
    134         final ArraySet<CastDevice> devices = new ArraySet<CastDevice>();
    135         synchronized (mProjectionLock) {
    136             if (mProjection != null) {
    137                 final CastDevice device = new CastDevice();
    138                 device.id = mProjection.getPackageName();
    139                 device.name = getAppName(mProjection.getPackageName());
    140                 device.description = mContext.getString(R.string.quick_settings_casting);
    141                 device.state = CastDevice.STATE_CONNECTED;
    142                 device.tag = mProjection;
    143                 devices.add(device);
    144                 return devices;
    145             }
    146         }
    147         synchronized(mRoutes) {
    148             for (RouteInfo route : mRoutes.values()) {
    149                 final CastDevice device = new CastDevice();
    150                 device.id = route.getTag().toString();
    151                 final CharSequence name = route.getName(mContext);
    152                 device.name = name != null ? name.toString() : null;
    153                 final CharSequence description = route.getDescription();
    154                 device.description = description != null ? description.toString() : null;
    155                 device.state = route.isConnecting() ? CastDevice.STATE_CONNECTING
    156                         : route.isSelected() ? CastDevice.STATE_CONNECTED
    157                         : CastDevice.STATE_DISCONNECTED;
    158                 device.tag = route;
    159                 devices.add(device);
    160             }
    161         }
    162         return devices;
    163     }
    164 
    165     @Override
    166     public void startCasting(CastDevice device) {
    167         if (device == null || device.tag == null) return;
    168         final RouteInfo route = (RouteInfo) device.tag;
    169         if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route));
    170         mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route);
    171     }
    172 
    173     @Override
    174     public void stopCasting(CastDevice device) {
    175         final boolean isProjection = device.tag instanceof MediaProjectionInfo;
    176         if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection);
    177         if (isProjection) {
    178             final MediaProjectionInfo projection = (MediaProjectionInfo) device.tag;
    179             if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
    180                 mProjectionManager.stopActiveProjection();
    181             } else {
    182                 Log.w(TAG, "Projection is no longer active: " + projection);
    183             }
    184         } else {
    185             mMediaRouter.getFallbackRoute().select();
    186         }
    187     }
    188 
    189     private void setProjection(MediaProjectionInfo projection, boolean started) {
    190         boolean changed = false;
    191         final MediaProjectionInfo oldProjection = mProjection;
    192         synchronized (mProjectionLock) {
    193             final boolean isCurrent = Objects.equals(projection, mProjection);
    194             if (started && !isCurrent) {
    195                 mProjection = projection;
    196                 changed = true;
    197             } else if (!started && isCurrent) {
    198                 mProjection = null;
    199                 changed = true;
    200             }
    201         }
    202         if (changed) {
    203             if (DEBUG) Log.d(TAG, "setProjection: " + oldProjection + " -> " + mProjection);
    204             fireOnCastDevicesChanged();
    205         }
    206     }
    207 
    208     private String getAppName(String packageName) {
    209         final PackageManager pm = mContext.getPackageManager();
    210         try {
    211             final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
    212             if (appInfo != null) {
    213                 final CharSequence label = appInfo.loadLabel(pm);
    214                 if (!TextUtils.isEmpty(label)) {
    215                     return label.toString();
    216                 }
    217             }
    218             Log.w(TAG, "No label found for package: " + packageName);
    219         } catch (NameNotFoundException e) {
    220             Log.w(TAG, "Error getting appName for package: " + packageName, e);
    221         }
    222         return packageName;
    223     }
    224 
    225     private void updateRemoteDisplays() {
    226         synchronized(mRoutes) {
    227             mRoutes.clear();
    228             final int n = mMediaRouter.getRouteCount();
    229             for (int i = 0; i < n; i++) {
    230                 final RouteInfo route = mMediaRouter.getRouteAt(i);
    231                 if (!route.isEnabled()) continue;
    232                 if (!route.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) continue;
    233                 ensureTagExists(route);
    234                 mRoutes.put(route.getTag().toString(), route);
    235             }
    236             final RouteInfo selected = mMediaRouter.getSelectedRoute(ROUTE_TYPE_REMOTE_DISPLAY);
    237             if (selected != null && !selected.isDefault()) {
    238                 ensureTagExists(selected);
    239                 mRoutes.put(selected.getTag().toString(), selected);
    240             }
    241         }
    242         fireOnCastDevicesChanged();
    243     }
    244 
    245     private void ensureTagExists(RouteInfo route) {
    246         if (route.getTag() == null) {
    247             route.setTag(UUID.randomUUID().toString());
    248         }
    249     }
    250 
    251     private void fireOnCastDevicesChanged() {
    252         for (Callback callback : mCallbacks) {
    253             fireOnCastDevicesChanged(callback);
    254         }
    255     }
    256 
    257     private void fireOnCastDevicesChanged(Callback callback) {
    258         callback.onCastDevicesChanged();
    259     }
    260 
    261     private static String routeToString(RouteInfo route) {
    262         if (route == null) return null;
    263         final StringBuilder sb = new StringBuilder().append(route.getName()).append('/')
    264                 .append(route.getDescription()).append('@').append(route.getDeviceAddress())
    265                 .append(",status=").append(route.getStatus());
    266         if (route.isDefault()) sb.append(",default");
    267         if (route.isEnabled()) sb.append(",enabled");
    268         if (route.isConnecting()) sb.append(",connecting");
    269         if (route.isSelected()) sb.append(",selected");
    270         return sb.append(",id=").append(route.getTag()).toString();
    271     }
    272 
    273     private final MediaRouter.SimpleCallback mMediaCallback = new MediaRouter.SimpleCallback() {
    274         @Override
    275         public void onRouteAdded(MediaRouter router, RouteInfo route) {
    276             if (DEBUG) Log.d(TAG, "onRouteAdded: " + routeToString(route));
    277             updateRemoteDisplays();
    278         }
    279         @Override
    280         public void onRouteChanged(MediaRouter router, RouteInfo route) {
    281             if (DEBUG) Log.d(TAG, "onRouteChanged: " + routeToString(route));
    282             updateRemoteDisplays();
    283         }
    284         @Override
    285         public void onRouteRemoved(MediaRouter router, RouteInfo route) {
    286             if (DEBUG) Log.d(TAG, "onRouteRemoved: " + routeToString(route));
    287             updateRemoteDisplays();
    288         }
    289         @Override
    290         public void onRouteSelected(MediaRouter router, int type, RouteInfo route) {
    291             if (DEBUG) Log.d(TAG, "onRouteSelected(" + type + "): " + routeToString(route));
    292             updateRemoteDisplays();
    293         }
    294         @Override
    295         public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
    296             if (DEBUG) Log.d(TAG, "onRouteUnselected(" + type + "): " + routeToString(route));
    297             updateRemoteDisplays();
    298         }
    299     };
    300 
    301     private final MediaProjectionManager.Callback mProjectionCallback
    302             = new MediaProjectionManager.Callback() {
    303         @Override
    304         public void onStart(MediaProjectionInfo info) {
    305             setProjection(info, true);
    306         }
    307 
    308         @Override
    309         public void onStop(MediaProjectionInfo info) {
    310             setProjection(info, false);
    311         }
    312     };
    313 }
    314