Merge "QS: Wire up screen casting to tile/panel." into lmp-dev
diff --git a/media/java/android/media/projection/MediaProjectionInfo.java b/media/java/android/media/projection/MediaProjectionInfo.java
index 7ebc31f..5a65e65 100644
--- a/media/java/android/media/projection/MediaProjectionInfo.java
+++ b/media/java/android/media/projection/MediaProjectionInfo.java
@@ -20,6 +20,8 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import java.util.Objects;
+
 /** @hide */
 public final class MediaProjectionInfo implements Parcelable {
     private final String mPackageName;
@@ -44,6 +46,21 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (o instanceof MediaProjectionInfo) {
+            final MediaProjectionInfo other = (MediaProjectionInfo) o;
+            return Objects.equals(other.mPackageName, mPackageName)
+                    && Objects.equals(other.mUserHandle, mUserHandle);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPackageName, mUserHandle);
+    }
+
+    @Override
     public String toString() {
         return "MediaProjectionInfo{mPackageName="
             + mPackageName + ", mUserHandle="
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 6cd0f39..f503657 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -239,7 +239,8 @@
         @Override
         public void onDetailItemDisconnect(Item item) {
             if (item == null || item.tag == null) return;
-            mController.stopCasting();
+            final CastDevice device = (CastDevice) item.tag;
+            mController.stopCasting(device);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index eb5804a..7713e57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -25,7 +25,7 @@
     void setCurrentUserId(int currentUserId);
     Set<CastDevice> getCastDevices();
     void startCasting(CastDevice device);
-    void stopCasting();
+    void stopCasting(CastDevice device);
 
     public interface Callback {
         void onCastDevicesChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 22179e0..eb0be05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -19,15 +19,25 @@
 import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.systemui.R;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 
@@ -41,12 +51,19 @@
     private final MediaRouter mMediaRouter;
     private final ArrayMap<String, RouteInfo> mRoutes = new ArrayMap<>();
     private final Object mDiscoveringLock = new Object();
+    private final MediaProjectionManager mProjectionManager;
+    private final Object mProjectionLock = new Object();
 
     private boolean mDiscovering;
+    private MediaProjectionInfo mProjection;
 
     public CastControllerImpl(Context context) {
         mContext = context;
         mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mProjectionManager = (MediaProjectionManager)
+                context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+        mProjection = mProjectionManager.getActiveProjectionInfo();
+        mProjectionManager.addCallback(mProjectionCallback, new Handler());
         if (DEBUG) Log.d(TAG, "new CastController()");
     }
 
@@ -59,6 +76,7 @@
             final RouteInfo route = mRoutes.valueAt(i);
             pw.print("    "); pw.println(routeToString(route));
         }
+        pw.print("  mProjection="); pw.println(mProjection);
     }
 
     @Override
@@ -95,6 +113,18 @@
     @Override
     public Set<CastDevice> getCastDevices() {
         final ArraySet<CastDevice> devices = new ArraySet<CastDevice>();
+        synchronized (mProjectionLock) {
+            if (mProjection != null) {
+                final CastDevice device = new CastDevice();
+                device.id = mProjection.getPackageName();
+                device.name = getAppName(mProjection.getPackageName());
+                device.description = mContext.getString(R.string.quick_settings_casting);
+                device.state = CastDevice.STATE_CONNECTED;
+                device.tag = mProjection;
+                devices.add(device);
+                return devices;
+            }
+        }
         synchronized(mRoutes) {
             for (RouteInfo route : mRoutes.values()) {
                 final CastDevice device = new CastDevice();
@@ -122,9 +152,55 @@
     }
 
     @Override
-    public void stopCasting() {
-        if (DEBUG) Log.d(TAG, "stopCasting");
-        mMediaRouter.getDefaultRoute().select();
+    public void stopCasting(CastDevice device) {
+        final boolean isProjection = device.tag instanceof MediaProjectionInfo;
+        if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection);
+        if (isProjection) {
+            final MediaProjectionInfo projection = (MediaProjectionInfo) device.tag;
+            if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
+                mProjectionManager.stopActiveProjection();
+            } else {
+                Log.w(TAG, "Projection is no longer active: " + projection);
+            }
+        } else {
+            mMediaRouter.getDefaultRoute().select();
+        }
+    }
+
+    private void setProjection(MediaProjectionInfo projection, boolean started) {
+        boolean changed = false;
+        final MediaProjectionInfo oldProjection = mProjection;
+        synchronized (mProjectionLock) {
+            final boolean isCurrent = Objects.equals(projection, mProjection);
+            if (started && !isCurrent) {
+                mProjection = projection;
+                changed = true;
+            } else if (!started && isCurrent) {
+                mProjection = null;
+                changed = true;
+            }
+        }
+        if (changed) {
+            if (DEBUG) Log.d(TAG, "setProjection: " + oldProjection + " -> " + mProjection);
+            fireOnCastDevicesChanged();
+        }
+    }
+
+    private String getAppName(String packageName) {
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
+            if (appInfo != null) {
+                final CharSequence label = appInfo.loadLabel(pm);
+                if (!TextUtils.isEmpty(label)) {
+                    return label.toString();
+                }
+            }
+            Log.w(TAG, "No label found for package: " + packageName);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "Error getting appName for package: " + packageName, e);
+        }
+        return packageName;
     }
 
     private void updateRemoteDisplays() {
@@ -202,4 +278,17 @@
             updateRemoteDisplays();
         }
     };
+
+    private final MediaProjectionManager.Callback mProjectionCallback
+            = new MediaProjectionManager.Callback() {
+        @Override
+        public void onStart(MediaProjectionInfo info) {
+            setProjection(info, true);
+        }
+
+        @Override
+        public void onStop(MediaProjectionInfo info) {
+            setProjection(info, false);
+        }
+    };
 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 69d1dc9..8ec9b254 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -111,7 +111,6 @@
             @Override
             public void binderDied() {
                 synchronized (mLock) {
-                    unlinkDeathRecipientLocked(callback);
                     removeCallback(callback);
                 }
             }
@@ -125,7 +124,7 @@
     private void removeCallback(IMediaProjectionWatcherCallback callback) {
         synchronized (mLock) {
             unlinkDeathRecipientLocked(callback);
-            removeCallback(callback);
+            mCallbackDelegate.remove(callback);
         }
     }