1af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock/*
2af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * Copyright (C) 2014 The Android Open Source Project
3af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *
4af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * Licensed under the Apache License, Version 2.0 (the "License");
5af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * you may not use this file except in compliance with the License.
6af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * You may obtain a copy of the License at
7af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *
8af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *      http://www.apache.org/licenses/LICENSE-2.0
9af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock *
10af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * Unless required by applicable law or agreed to in writing, software
11af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * distributed under the License is distributed on an "AS IS" BASIS,
12af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * See the License for the specific language governing permissions and
14af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock * limitations under the License.
15af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock */
16af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
17af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockpackage com.android.systemui.statusbar.policy;
18af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
19af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.content.Context;
2078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport android.content.pm.ApplicationInfo;
2178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport android.content.pm.PackageManager;
2278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport android.content.pm.PackageManager.NameNotFoundException;
23af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.media.MediaRouter;
24af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport android.media.MediaRouter.RouteInfo;
2578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport android.media.projection.MediaProjectionInfo;
2678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport android.media.projection.MediaProjectionManager;
2778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport android.os.Handler;
2878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport android.text.TextUtils;
291e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlockimport android.util.ArrayMap;
301e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlockimport android.util.ArraySet;
311e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlockimport android.util.Log;
32af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
3378b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport com.android.systemui.R;
3478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock
351e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlockimport java.io.FileDescriptor;
361e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlockimport java.io.PrintWriter;
37af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockimport java.util.ArrayList;
3878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlockimport java.util.Objects;
391e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlockimport java.util.Set;
401e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlockimport java.util.UUID;
41af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
42c0d7058b14c24cd07912f5629c26b39b7b4673d5Winsonimport static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
43c0d7058b14c24cd07912f5629c26b39b7b4673d5Winson
44af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock/** Platform implementation of the cast controller. **/
45af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlockpublic class CastControllerImpl implements CastController {
461e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private static final String TAG = "CastController";
471e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
48af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
491e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private final Context mContext;
50af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
51af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    private final MediaRouter mMediaRouter;
521e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private final ArrayMap<String, RouteInfo> mRoutes = new ArrayMap<>();
531e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private final Object mDiscoveringLock = new Object();
5478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    private final MediaProjectionManager mProjectionManager;
5578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    private final Object mProjectionLock = new Object();
56af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
571e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private boolean mDiscovering;
587eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk    private boolean mCallbackRegistered;
5978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    private MediaProjectionInfo mProjection;
60ccb6b9a90f228cc4e31a9442ed28756ff474c080John Spurlock
61af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public CastControllerImpl(Context context) {
621e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        mContext = context;
63af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
6478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        mProjectionManager = (MediaProjectionManager)
6578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
6678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        mProjection = mProjectionManager.getActiveProjectionInfo();
6778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        mProjectionManager.addCallback(mProjectionCallback, new Handler());
681e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (DEBUG) Log.d(TAG, "new CastController()");
691e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    }
701e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock
711e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
721e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        pw.println("CastController state:");
731e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        pw.print("  mDiscovering="); pw.println(mDiscovering);
747eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        pw.print("  mCallbackRegistered="); pw.println(mCallbackRegistered);
751e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
761e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        pw.print("  mRoutes.size="); pw.println(mRoutes.size());
771e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        for (int i = 0; i < mRoutes.size(); i++) {
781e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            final RouteInfo route = mRoutes.valueAt(i);
791e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            pw.print("    "); pw.println(routeToString(route));
801e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        }
8178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        pw.print("  mProjection="); pw.println(mProjection);
82af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
83af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
84af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
85af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public void addCallback(Callback callback) {
86af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        mCallbacks.add(callback);
871e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        fireOnCastDevicesChanged(callback);
887eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        synchronized (mDiscoveringLock) {
897eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            handleDiscoveryChangeLocked();
907eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        }
91af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
92af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
93af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
94af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public void removeCallback(Callback callback) {
95af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        mCallbacks.remove(callback);
967eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        synchronized (mDiscoveringLock) {
977eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            handleDiscoveryChangeLocked();
987eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        }
99af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
100af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
101af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
102af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public void setDiscovering(boolean request) {
1031e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        synchronized (mDiscoveringLock) {
1041e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (mDiscovering == request) return;
1051e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            mDiscovering = request;
1061e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (DEBUG) Log.d(TAG, "setDiscovering: " + request);
1077eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            handleDiscoveryChangeLocked();
1087eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        }
1097eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk    }
1107eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk
1117eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk    private void handleDiscoveryChangeLocked() {
1127eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        if (mCallbackRegistered) {
1137eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            mMediaRouter.removeCallback(mMediaCallback);
1147eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            mCallbackRegistered = false;
1157eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        }
1167eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        if (mDiscovering) {
1177eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
1187eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
1197eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            mCallbackRegistered = true;
1207eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk        } else if (mCallbacks.size() != 0) {
1217eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
1227eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk                    MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
1237eb6202feef939d92b5ea268cc2cfea3cc177cd6Jason Monk            mCallbackRegistered = true;
124af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
125af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
126af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
127af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    @Override
128af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    public void setCurrentUserId(int currentUserId) {
129af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        mMediaRouter.rebindAsUser(currentUserId);
130af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
131af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
1321e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    @Override
1331e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    public Set<CastDevice> getCastDevices() {
1341e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        final ArraySet<CastDevice> devices = new ArraySet<CastDevice>();
13578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        synchronized (mProjectionLock) {
13678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            if (mProjection != null) {
13778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                final CastDevice device = new CastDevice();
13878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                device.id = mProjection.getPackageName();
13978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                device.name = getAppName(mProjection.getPackageName());
14078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                device.description = mContext.getString(R.string.quick_settings_casting);
14178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                device.state = CastDevice.STATE_CONNECTED;
14278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                device.tag = mProjection;
14378b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                devices.add(device);
14478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                return devices;
14578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            }
14678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        }
1471e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        synchronized(mRoutes) {
1481e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            for (RouteInfo route : mRoutes.values()) {
1491e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                final CastDevice device = new CastDevice();
1501e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                device.id = route.getTag().toString();
1511e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                final CharSequence name = route.getName(mContext);
1521e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                device.name = name != null ? name.toString() : null;
1531e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                final CharSequence description = route.getDescription();
1541e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                device.description = description != null ? description.toString() : null;
1551e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                device.state = route.isConnecting() ? CastDevice.STATE_CONNECTING
1561e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                        : route.isSelected() ? CastDevice.STATE_CONNECTED
1571e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                        : CastDevice.STATE_DISCONNECTED;
1581e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                device.tag = route;
1591e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                devices.add(device);
1601e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            }
161af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
1621e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        return devices;
1631e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    }
1641e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock
1651e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    @Override
1661e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    public void startCasting(CastDevice device) {
1671e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (device == null || device.tag == null) return;
1681e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        final RouteInfo route = (RouteInfo) device.tag;
1691e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route));
1701e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route);
1711e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    }
172af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
1731e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    @Override
17478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    public void stopCasting(CastDevice device) {
17578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        final boolean isProjection = device.tag instanceof MediaProjectionInfo;
17678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection);
17778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        if (isProjection) {
17878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            final MediaProjectionInfo projection = (MediaProjectionInfo) device.tag;
17978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
18078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                mProjectionManager.stopActiveProjection();
18178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            } else {
18278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                Log.w(TAG, "Projection is no longer active: " + projection);
18378b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            }
18478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        } else {
18578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            mMediaRouter.getDefaultRoute().select();
18678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        }
18778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    }
18878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock
18978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    private void setProjection(MediaProjectionInfo projection, boolean started) {
19078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        boolean changed = false;
19178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        final MediaProjectionInfo oldProjection = mProjection;
19278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        synchronized (mProjectionLock) {
19378b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            final boolean isCurrent = Objects.equals(projection, mProjection);
19478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            if (started && !isCurrent) {
19578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                mProjection = projection;
19678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                changed = true;
19778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            } else if (!started && isCurrent) {
19878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                mProjection = null;
19978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                changed = true;
20078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            }
20178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        }
20278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        if (changed) {
20378b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            if (DEBUG) Log.d(TAG, "setProjection: " + oldProjection + " -> " + mProjection);
20478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            fireOnCastDevicesChanged();
20578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        }
20678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    }
20778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock
20878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    private String getAppName(String packageName) {
20978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        final PackageManager pm = mContext.getPackageManager();
21078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        try {
21178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
21278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            if (appInfo != null) {
21378b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                final CharSequence label = appInfo.loadLabel(pm);
21478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                if (!TextUtils.isEmpty(label)) {
21578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                    return label.toString();
21678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock                }
21778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            }
21878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            Log.w(TAG, "No label found for package: " + packageName);
21978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        } catch (NameNotFoundException e) {
22078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            Log.w(TAG, "Error getting appName for package: " + packageName, e);
22178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        }
22278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        return packageName;
2231e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    }
2241e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock
2251e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private void updateRemoteDisplays() {
2261e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        synchronized(mRoutes) {
2271e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            mRoutes.clear();
2281e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            final int n = mMediaRouter.getRouteCount();
2291e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            for (int i = 0; i < n; i++) {
2301e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                final RouteInfo route = mMediaRouter.getRouteAt(i);
2311e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                if (!route.isEnabled()) continue;
2321e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                if (!route.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) continue;
2331e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                ensureTagExists(route);
2341e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                mRoutes.put(route.getTag().toString(), route);
2351e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            }
2361e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            final RouteInfo selected = mMediaRouter.getSelectedRoute(ROUTE_TYPE_REMOTE_DISPLAY);
2371e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (selected != null && !selected.isDefault()) {
2381e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                ensureTagExists(selected);
2391e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                mRoutes.put(selected.getTag().toString(), selected);
2401e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            }
241af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
2421e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        fireOnCastDevicesChanged();
2431e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    }
2441e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock
2451e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private void ensureTagExists(RouteInfo route) {
2461e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (route.getTag() == null) {
2471e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            route.setTag(UUID.randomUUID().toString());
248ccb6b9a90f228cc4e31a9442ed28756ff474c080John Spurlock        }
249af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
250af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
2511e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private void fireOnCastDevicesChanged() {
252af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        for (Callback callback : mCallbacks) {
2531e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            fireOnCastDevicesChanged(callback);
254ccb6b9a90f228cc4e31a9442ed28756ff474c080John Spurlock        }
255ccb6b9a90f228cc4e31a9442ed28756ff474c080John Spurlock    }
256ccb6b9a90f228cc4e31a9442ed28756ff474c080John Spurlock
2571e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private void fireOnCastDevicesChanged(Callback callback) {
2581e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        callback.onCastDevicesChanged();
2591e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    }
2601e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock
2611e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock    private static String routeToString(RouteInfo route) {
2621e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (route == null) return null;
2631e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        final StringBuilder sb = new StringBuilder().append(route.getName()).append('/')
2641e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                .append(route.getDescription()).append('@').append(route.getDeviceAddress())
2651e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock                .append(",status=").append(route.getStatus());
2661e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (route.isDefault()) sb.append(",default");
2671e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (route.isEnabled()) sb.append(",enabled");
2681e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (route.isConnecting()) sb.append(",connecting");
2691e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        if (route.isSelected()) sb.append(",selected");
2701e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock        return sb.append(",id=").append(route.getTag()).toString();
271af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    }
272af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock
273af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    private final MediaRouter.SimpleCallback mMediaCallback = new MediaRouter.SimpleCallback() {
274af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        @Override
275af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        public void onRouteAdded(MediaRouter router, RouteInfo route) {
2761e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (DEBUG) Log.d(TAG, "onRouteAdded: " + routeToString(route));
277af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            updateRemoteDisplays();
278af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
279af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        @Override
280af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        public void onRouteChanged(MediaRouter router, RouteInfo route) {
2811e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (DEBUG) Log.d(TAG, "onRouteChanged: " + routeToString(route));
282af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            updateRemoteDisplays();
283af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
284af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        @Override
285af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
2861e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (DEBUG) Log.d(TAG, "onRouteRemoved: " + routeToString(route));
287af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            updateRemoteDisplays();
288af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
289af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        @Override
290af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        public void onRouteSelected(MediaRouter router, int type, RouteInfo route) {
2911e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (DEBUG) Log.d(TAG, "onRouteSelected(" + type + "): " + routeToString(route));
292af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            updateRemoteDisplays();
293af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
294af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        @Override
295af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
2961e6eb17a22056529601c8e413c2da0541d59d93bJohn Spurlock            if (DEBUG) Log.d(TAG, "onRouteUnselected(" + type + "): " + routeToString(route));
297af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock            updateRemoteDisplays();
298af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock        }
299af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock    };
30078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock
30178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    private final MediaProjectionManager.Callback mProjectionCallback
30278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            = new MediaProjectionManager.Callback() {
30378b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        @Override
30478b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        public void onStart(MediaProjectionInfo info) {
30578b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            setProjection(info, true);
30678b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        }
30778b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock
30878b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        @Override
30978b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        public void onStop(MediaProjectionInfo info) {
31078b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock            setProjection(info, false);
31178b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock        }
31278b8c8fbde12b214314cc2ab2644350b5d5189a5John Spurlock    };
313af8d6c44f06d2f8baac2c5774a9efdae3fc36797John Spurlock}
314