OSDN Git Service

Add MediaRouter API to get presentation display.
authorJeff Brown <jeffbrown@google.com>
Thu, 25 Oct 2012 04:28:33 +0000 (21:28 -0700)
committerJeff Brown <jeffbrown@google.com>
Fri, 26 Oct 2012 03:31:21 +0000 (20:31 -0700)
This new API makes it possible for an application to ask on
which Display it should show a Presentation based on the currently
selected media route.

Also added a new API on DisplayManager to query displays that
support a certain category of uses.

Improved the documentation of the Presentation class to explain
how to choose an appropriate Display for presentation.

Bug: 7409073
Change-Id: Iab451215e570ae55f3718fc228303143c800fe51

13 files changed:
api/17.txt
api/current.txt
core/java/android/app/Presentation.java
core/java/android/hardware/display/DisplayManager.java
core/java/android/view/Display.java
core/java/android/view/DisplayInfo.java
media/java/android/media/MediaRouter.java
services/java/com/android/server/display/DisplayDeviceInfo.java
services/java/com/android/server/display/HeadlessDisplayAdapter.java
services/java/com/android/server/display/LocalDisplayAdapter.java
services/java/com/android/server/display/LogicalDisplay.java
services/java/com/android/server/display/OverlayDisplayAdapter.java
services/java/com/android/server/display/WifiDisplayAdapter.java

index 85ae413..e26d8f0 100644 (file)
@@ -10093,8 +10093,10 @@ package android.hardware.display {
   public final class DisplayManager {
     method public android.view.Display getDisplay(int);
     method public android.view.Display[] getDisplays();
+    method public android.view.Display[] getDisplays(java.lang.String);
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
+    field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
   }
 
   public static abstract interface DisplayManager.DisplayListener {
@@ -11768,6 +11770,7 @@ package android.media {
     method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
+    method public void onRoutePresentationDisplayChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
@@ -11802,6 +11805,7 @@ package android.media {
     method public java.lang.CharSequence getName(android.content.Context);
     method public int getPlaybackStream();
     method public int getPlaybackType();
+    method public android.view.Display getPresentationDisplay();
     method public java.lang.CharSequence getStatus();
     method public int getSupportedTypes();
     method public java.lang.Object getTag();
index 85ae413..e26d8f0 100644 (file)
@@ -10093,8 +10093,10 @@ package android.hardware.display {
   public final class DisplayManager {
     method public android.view.Display getDisplay(int);
     method public android.view.Display[] getDisplays();
+    method public android.view.Display[] getDisplays(java.lang.String);
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
+    field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
   }
 
   public static abstract interface DisplayManager.DisplayListener {
@@ -11768,6 +11770,7 @@ package android.media {
     method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
+    method public void onRoutePresentationDisplayChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
@@ -11802,6 +11805,7 @@ package android.media {
     method public java.lang.CharSequence getName(android.content.Context);
     method public int getPlaybackStream();
     method public int getPlaybackType();
+    method public android.view.Display getPresentationDisplay();
     method public java.lang.CharSequence getStatus();
     method public int getSupportedTypes();
     method public java.lang.Object getTag();
index 20b27c5..3e8af60 100644 (file)
@@ -50,6 +50,93 @@ import android.util.TypedValue;
  * whenever the activity itself is paused or resumed.
  * </p>
  *
+ * <h3>Choosing a presentation display</h3>
+ * <p>
+ * Before showing a {@link Presentation} it's important to choose the {@link Display}
+ * on which it will appear.  Choosing a presentation display is sometimes difficult
+ * because there may be multiple displays attached.  Rather than trying to guess
+ * which display is best, an application should let the system choose a suitable
+ * presentation display.
+ * </p><p>
+ * There are two main ways to choose a {@link Display}.
+ * </p>
+ *
+ * <h4>Using the media router to choose a presentation display</h4>
+ * <p>
+ * The easiest way to choose a presentation display is to use the
+ * {@link android.media.MediaRouter MediaRouter} API.  The media router service keeps
+ * track of which audio and video routes are available on the system.
+ * The media router sends notifications whenever routes are selected or unselected
+ * or when the preferred presentation display of a route changes.
+ * So an application can simply watch for these notifications and show or dismiss
+ * a presentation on the preferred presentation display automatically.
+ * </p><p>
+ * The preferred presentation display is the display that the media router recommends
+ * that the application should use if it wants to show content on the secondary display.
+ * Sometimes there may not be a preferred presentation display in which
+ * case the application should show its content locally without using a presentation.
+ * </p><p>
+ * Here's how to use the media router to create and show a presentation on the preferred
+ * presentation display using {@link android.media.MediaRouter.RouteInfo#getPresentationDisplay()}.
+ * </p>
+ * {@samplecode
+ * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
+ * if (route != null) $&#123;
+ *     Display presentationDisplay = route.getPresentationDisplay();
+ *     if (presentationDisplay != null) $&#123;
+ *         Presentation presentation = new MyPresentation(context, presentationDisplay);
+ *         presentation.show();
+ *     $&#125;
+ * $&#125;
+ * }
+ * <p>
+ * The following sample code from <code>ApiDemos</code> demonstrates how to use the media
+ * router to automatically switch between showing content in the main activity and showing
+ * the content in a presentation when a presentation display is available.
+ * </p>
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java
+ *      activity}
+ *
+ * <h4>Using the display manager to choose a presentation display</h4>
+ * <p>
+ * Another way to choose a presentation display is to use the {@link DisplayManager} API
+ * directly.  The display manager service provides functions to enumerate and describe all
+ * displays that are attached to the system including displays that may be used
+ * for presentations.
+ * </p><p>
+ * The display manager keeps track of all displays in the system.  However, not all
+ * displays are appropriate for showing presentations.  For example, if an activity
+ * attempted to show a presentation on the main display it might obscure its own content
+ * (it's like opening a dialog on top of your activity).
+ * </p><p>
+ * Here's how to identify suitable displays for showing presentations using
+ * {@link DisplayManager#getDisplays(String)} and the
+ * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION} category.
+ * </p>
+ * {@samplecode
+ * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
+ * if (presentationDisplays.length > 0) $&#123;
+ *     // If there is more than one suitable presentation display, then we could consider
+ *     // giving the user a choice.  For this example, we simply choose the first display
+ *     // which is the one the system recommends as the preferred presentation display.
+ *     Display display = presentationDisplays[0];
+ *     Presentation presentation = new MyPresentation(context, presentationDisplay);
+ *     presentation.show();
+ * $&#125;
+ * }
+ * <p>
+ * The following sample code from <code>ApiDemos</code> demonstrates how to use the display
+ * manager to enumerate displays and show content on multiple presentation displays
+ * simultaneously.
+ * </p>
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java
+ *      activity}
+ *
+ * @see android.media.MediaRouter#ROUTE_TYPE_LIVE_VIDEO for information on about live
+ * video routes and how to obtain the preferred presentation display for the
+ * current media route.
  * @see DisplayManager for information on how to enumerate displays and receive
  * notifications when displays are added or removed.
  */
@@ -121,7 +208,7 @@ public class Presentation extends Dialog {
     @Override
     protected void onStart() {
         super.onStart();
-        mDisplayManager.registerDisplayListener(mDisplayListener, null);
+        mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
 
         // Since we were not watching for display changes until just now, there is a
         // chance that the display metrics have changed.  If so, we will need to
index 28e320b..0a7a2e7 100644 (file)
@@ -21,6 +21,8 @@ import android.os.Handler;
 import android.util.SparseArray;
 import android.view.Display;
 
+import java.util.ArrayList;
+
 /**
  * Manages the properties of attached displays.
  * <p>
@@ -40,6 +42,8 @@ public final class DisplayManager {
     private final Object mLock = new Object();
     private final SparseArray<Display> mDisplays = new SparseArray<Display>();
 
+    private final ArrayList<Display> mTempDisplays = new ArrayList<Display>();
+
     /**
      * Broadcast receiver that indicates when the Wifi display status changes.
      * <p>
@@ -60,6 +64,20 @@ public final class DisplayManager {
     public static final String EXTRA_WIFI_DISPLAY_STATUS =
             "android.hardware.display.extra.WIFI_DISPLAY_STATUS";
 
+    /**
+     * Display category: Presentation displays.
+     * <p>
+     * This category can be used to identify secondary displays that are suitable for
+     * use as presentation displays.
+     * </p>
+     *
+     * @see android.app.Presentation for information about presenting content
+     * on secondary displays.
+     * @see #getDisplays(String)
+     */
+    public static final String DISPLAY_CATEGORY_PRESENTATION =
+            "android.hardware.display.category.PRESENTATION";
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
@@ -87,24 +105,52 @@ public final class DisplayManager {
      * @return An array containing all displays.
      */
     public Display[] getDisplays() {
-        int[] displayIds = mGlobal.getDisplayIds();
-        int expectedCount = displayIds.length;
-        Display[] displays = new Display[expectedCount];
+        return getDisplays(null);
+    }
+
+    /**
+     * Gets all currently valid logical displays of the specified category.
+     * <p>
+     * When there are multiple displays in a category the returned displays are sorted
+     * of preference.  For example, if the requested category is
+     * {@link #DISPLAY_CATEGORY_PRESENTATION} and there are multiple presentation displays
+     * then the displays are sorted so that the first display in the returned array
+     * is the most preferred presentation display.  The application may simply
+     * use the first display or allow the user to choose.
+     * </p>
+     *
+     * @param category The requested display category or null to return all displays.
+     * @return An array containing all displays sorted by order of preference.
+     *
+     * @see #DISPLAY_CATEGORY_PRESENTATION
+     */
+    public Display[] getDisplays(String category) {
+        final int[] displayIds = mGlobal.getDisplayIds();
         synchronized (mLock) {
-            int actualCount = 0;
-            for (int i = 0; i < expectedCount; i++) {
-                Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
-                if (display != null) {
-                    displays[actualCount++] = display;
+            try {
+                if (category == null) {
+                    addMatchingDisplaysLocked(mTempDisplays, displayIds, -1);
+                } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
+                    addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
+                    addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI);
+                    addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
                 }
+                return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
+            } finally {
+                mTempDisplays.clear();
             }
-            if (actualCount != expectedCount) {
-                Display[] oldDisplays = displays;
-                displays = new Display[actualCount];
-                System.arraycopy(oldDisplays, 0, displays, 0, actualCount);
+        }
+    }
+
+    private void addMatchingDisplaysLocked(
+            ArrayList<Display> displays, int[] displayIds, int matchType) {
+        for (int i = 0; i < displayIds.length; i++) {
+            Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
+            if (display != null
+                    && (matchType < 0 || display.getType() == matchType)) {
+                displays.add(display);
             }
         }
-        return displays;
     }
 
     private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) {
index 1cd3e05..758abb5 100644 (file)
@@ -54,7 +54,9 @@ public final class Display {
     private final DisplayManagerGlobal mGlobal;
     private final int mDisplayId;
     private final int mLayerStack;
-    private final String mName;
+    private final int mFlags;
+    private final int mType;
+    private final String mAddress;
     private final CompatibilityInfoHolder mCompatibilityInfo;
 
     private DisplayInfo mDisplayInfo; // never null
@@ -141,6 +143,36 @@ public final class Display {
     public static final int FLAG_SECURE = 1 << 1;
 
     /**
+     * Display type: Unknown display type.
+     * @hide
+     */
+    public static final int TYPE_UNKNOWN = 0;
+
+    /**
+     * Display type: Built-in display.
+     * @hide
+     */
+    public static final int TYPE_BUILT_IN = 1;
+
+    /**
+     * Display type: HDMI display.
+     * @hide
+     */
+    public static final int TYPE_HDMI = 2;
+
+    /**
+     * Display type: WiFi display.
+     * @hide
+     */
+    public static final int TYPE_WIFI = 3;
+
+    /**
+     * Display type: Overlay display.
+     * @hide
+     */
+    public static final int TYPE_OVERLAY = 4;
+
+    /**
      * Internal method to create a display.
      * Applications should use {@link android.view.WindowManager#getDefaultDisplay()}
      * or {@link android.hardware.display.DisplayManager#getDisplay}
@@ -154,10 +186,14 @@ public final class Display {
         mGlobal = global;
         mDisplayId = displayId;
         mDisplayInfo = displayInfo;
-        mLayerStack = displayInfo.layerStack; // can never change as long as the display is valid
-        mName = displayInfo.name; // cannot change as long as the display is valid
         mCompatibilityInfo = compatibilityInfo;
         mIsValid = true;
+
+        // Cache properties that cannot change as long as the display is valid.
+        mLayerStack = displayInfo.layerStack;
+        mFlags = displayInfo.flags;
+        mType = displayInfo.type;
+        mAddress = displayInfo.address;
     }
 
     /**
@@ -228,10 +264,34 @@ public final class Display {
      * @see #FLAG_SECURE
      */
     public int getFlags() {
-        synchronized (this) {
-            updateDisplayInfoLocked();
-            return mDisplayInfo.flags;
-        }
+        return mFlags;
+    }
+
+    /**
+     * Gets the display type.
+     *
+     * @return The display type.
+     *
+     * @see #TYPE_UNKNOWN
+     * @see #TYPE_BUILT_IN
+     * @see #TYPE_HDMI
+     * @see #TYPE_WIFI
+     * @see #TYPE_OVERLAY
+     * @hide
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Gets the display address, or null if none.
+     * Interpretation varies by display type.
+     *
+     * @return The display address.
+     * @hide
+     */
+    public String getAddress() {
+        return mAddress;
     }
 
     /**
@@ -246,10 +306,17 @@ public final class Display {
 
     /**
      * Gets the name of the display.
+     * <p>
+     * Note that some displays may be renamed by the user.
+     * </p>
+     *
      * @return The display's name.
      */
     public String getName() {
-        return mName;
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.name;
+        }
     }
 
     /**
@@ -527,5 +594,25 @@ public final class Display {
                     + ", " + mTempMetrics + ", isValid=" + mIsValid;
         }
     }
+
+    /**
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_UNKNOWN:
+                return "UNKNOWN";
+            case TYPE_BUILT_IN:
+                return "BUILT_IN";
+            case TYPE_HDMI:
+                return "HDMI";
+            case TYPE_WIFI:
+                return "WIFI";
+            case TYPE_OVERLAY:
+                return "OVERLAY";
+            default:
+                return Integer.toString(type);
+        }
+    }
 }
 
index ead5ff4..f3841d5 100644 (file)
@@ -39,6 +39,17 @@ public final class DisplayInfo implements Parcelable {
     public int flags;
 
     /**
+     * Display type.
+     */
+    public int type;
+
+    /**
+     * Display address, or null if none.
+     * Interpretation varies by display type.
+     */
+    public String address;
+
+    /**
      * The human-readable name of the display.
      */
     public String name;
@@ -143,10 +154,12 @@ public final class DisplayInfo implements Parcelable {
     public float physicalYDpi;
 
     public static final Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
+        @Override
         public DisplayInfo createFromParcel(Parcel source) {
             return new DisplayInfo(source);
         }
 
+        @Override
         public DisplayInfo[] newArray(int size) {
             return new DisplayInfo[size];
         }
@@ -171,6 +184,9 @@ public final class DisplayInfo implements Parcelable {
     public boolean equals(DisplayInfo other) {
         return other != null
                 && layerStack == other.layerStack
+                && flags == other.flags
+                && type == other.type
+                && Objects.equal(address, other.address)
                 && Objects.equal(name, other.name)
                 && appWidth == other.appWidth
                 && appHeight == other.appHeight
@@ -195,6 +211,8 @@ public final class DisplayInfo implements Parcelable {
     public void copyFrom(DisplayInfo other) {
         layerStack = other.layerStack;
         flags = other.flags;
+        type = other.type;
+        address = other.address;
         name = other.name;
         appWidth = other.appWidth;
         appHeight = other.appHeight;
@@ -214,6 +232,8 @@ public final class DisplayInfo implements Parcelable {
     public void readFromParcel(Parcel source) {
         layerStack = source.readInt();
         flags = source.readInt();
+        type = source.readInt();
+        address = source.readString();
         name = source.readString();
         appWidth = source.readInt();
         appHeight = source.readInt();
@@ -234,6 +254,8 @@ public final class DisplayInfo implements Parcelable {
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(layerStack);
         dest.writeInt(this.flags);
+        dest.writeInt(type);
+        dest.writeString(address);
         dest.writeString(name);
         dest.writeInt(appWidth);
         dest.writeInt(appHeight);
@@ -294,7 +316,10 @@ public final class DisplayInfo implements Parcelable {
                 + ", rotation " + rotation
                 + ", density " + logicalDensityDpi
                 + ", " + physicalXDpi + " x " + physicalYDpi + " dpi"
-                + ", layerStack " + layerStack + flagsToString(flags) + "}";
+                + ", layerStack " + layerStack
+                + ", type " + Display.typeToString(type)
+                + ", address " + address
+                + flagsToString(flags) + "}";
     }
 
     private static String flagsToString(int flags) {
index e08d494..2a5a16e 100644 (file)
@@ -32,7 +32,6 @@ import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.Display;
-import android.view.DisplayInfo;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -53,7 +52,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 public class MediaRouter {
     private static final String TAG = "MediaRouter";
 
-    static class Static {
+    static class Static implements DisplayManager.DisplayListener {
         final Resources mResources;
         final IAudioService mAudioService;
         final DisplayManager mDisplayService;
@@ -105,6 +104,8 @@ public class MediaRouter {
             mDefaultAudioVideo = new RouteInfo(mSystemCategory);
             mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
             mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
+            mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute(
+                    mDefaultAudioVideo, getAllPresentationDisplays());
             addRouteStatic(mDefaultAudioVideo);
 
             // This will select the active wifi display route if there is one.
@@ -115,6 +116,8 @@ public class MediaRouter {
             appContext.registerReceiver(new VolumeChangeReceiver(),
                     new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
 
+            mDisplayService.registerDisplayListener(this, mHandler);
+
             AudioRoutesInfo newAudioRoutes = null;
             try {
                 newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
@@ -191,6 +194,39 @@ public class MediaRouter {
                 }
             }
         }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+            updatePresentationDisplays(displayId);
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            updatePresentationDisplays(displayId);
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            updatePresentationDisplays(displayId);
+        }
+
+        public Display[] getAllPresentationDisplays() {
+            return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
+        }
+
+        private void updatePresentationDisplays(int changedDisplayId) {
+            final Display[] displays = getAllPresentationDisplays();
+            final int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                final RouteInfo info = mRoutes.get(i);
+                Display display = choosePresentationDisplayForRoute(info, displays);
+                if (display != info.mPresentationDisplay
+                        || (display != null && display.getDisplayId() == changedDisplayId)) {
+                    info.mPresentationDisplay = display;
+                    dispatchRoutePresentationDisplayChanged(info);
+                }
+            }
+        }
     }
 
     static Static sStatic;
@@ -218,6 +254,9 @@ public class MediaRouter {
      * While remote routing is active the application may use a
      * {@link android.app.Presentation Presentation} to replace the mirrored view
      * on the external display with different content.</p>
+     *
+     * @see RouteInfo#getPresentationDisplay()
+     * @see android.app.Presentation
      */
     public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
 
@@ -674,6 +713,14 @@ public class MediaRouter {
         }
     }
 
+    static void dispatchRoutePresentationDisplayChanged(RouteInfo info) {
+        for (CallbackInfo cbi : sStatic.mCallbacks) {
+            if ((cbi.type & info.mSupportedTypes) != 0) {
+                cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info);
+            }
+        }
+    }
+
     static void systemVolumeChanged(int newValue) {
         final RouteInfo selectedRoute = sStatic.mSelectedRoute;
         if (selectedRoute == null) return;
@@ -755,6 +802,9 @@ public class MediaRouter {
         newRoute.mEnabled = available;
 
         newRoute.mName = display.getFriendlyDisplayName();
+
+        newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute,
+                sStatic.getAllPresentationDisplays());
         return newRoute;
     }
 
@@ -830,6 +880,27 @@ public class MediaRouter {
         return null;
     }
 
+    private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) {
+        if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
+            if (route.mDeviceAddress != null) {
+                // Find the indicated Wifi display by its address.
+                for (Display display : displays) {
+                    if (display.getType() == Display.TYPE_WIFI
+                            && route.mDeviceAddress.equals(display.getAddress())) {
+                        return display;
+                    }
+                }
+                return null;
+            }
+
+            if (route == sStatic.mDefaultAudioVideo && displays.length > 0) {
+                // Choose the first presentation display from the list.
+                return displays[0];
+            }
+        }
+        return null;
+    }
+
     /**
      * Information about a media route.
      */
@@ -848,6 +919,7 @@ public class MediaRouter {
         int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
         int mPlaybackStream = AudioManager.STREAM_MUSIC;
         VolumeCallbackInfo mVcb;
+        Display mPresentationDisplay;
 
         String mDeviceAddress;
         boolean mEnabled = true;
@@ -1119,6 +1191,38 @@ public class MediaRouter {
         }
 
         /**
+         * Gets the {@link Display} that should be used by the application to show
+         * a {@link android.app.Presentation} on an external display when this route is selected.
+         * Depending on the route, this may only be valid if the route is currently
+         * selected.
+         * <p>
+         * The preferred presentation display may change independently of the route
+         * being selected or unselected.  For example, the presentation display
+         * of the default system route may change when an external HDMI display is connected
+         * or disconnected even though the route itself has not changed.
+         * </p><p>
+         * This method may return null if there is no external display associated with
+         * the route or if the display is not ready to show UI yet.
+         * </p><p>
+         * The application should listen for changes to the presentation display
+         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
+         * show or dismiss its {@link android.app.Presentation} accordingly when the display
+         * becomes available or is removed.
+         * </p><p>
+         * This method only makes sense for {@link #ROUTE_TYPE_LIVE_VIDEO live video} routes.
+         * </p>
+         *
+         * @return The preferred presentation display to use when this route is
+         * selected or null if none.
+         *
+         * @see #ROUTE_TYPE_LIVE_VIDEO
+         * @see android.app.Presentation
+         */
+        public Display getPresentationDisplay() {
+            return mPresentationDisplay;
+        }
+
+        /**
          * @return true if this route is enabled and may be selected
          */
         public boolean isEnabled() {
@@ -1159,9 +1263,11 @@ public class MediaRouter {
         @Override
         public String toString() {
             String supportedTypes = typesToString(getSupportedTypes());
-            return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() +
-                    " category=" + getCategory() +
-                    " supportedTypes=" + supportedTypes + "}";
+            return getClass().getSimpleName() + "{ name=" + getName() +
+                    ", status=" + getStatus() +
+                    ", category=" + getCategory() +
+                    ", supportedTypes=" + supportedTypes +
+                    ", presentationDisplay=" + mPresentationDisplay + "}";
         }
     }
 
@@ -1856,6 +1962,21 @@ public class MediaRouter {
          * @param info The route with altered volume
          */
         public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
+
+        /**
+         * Called when a route's presentation display changes.
+         * <p>
+         * This method is called whenever the route's presentation display becomes
+         * available, is removes or has changes to some of its properties (such as its size).
+         * </p>
+         *
+         * @param router the MediaRouter reporting the event
+         * @param info The route whose presentation display changed
+         *
+         * @see RouteInfo#getPresentationDisplay()
+         */
+        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
+        }
     }
 
     /**
index e76bf44..247d8a0 100644 (file)
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.Surface;
 
 import libcore.util.Objects;
@@ -138,6 +139,17 @@ final class DisplayDeviceInfo {
      */
     public int rotation = Surface.ROTATION_0;
 
+    /**
+     * Display type.
+     */
+    public int type;
+
+    /**
+     * Display address, or null if none.
+     * Interpretation varies by display type.
+     */
+    public String address;
+
     public void setAssumedDensityForExternalDisplay(int width, int height) {
         densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
         // Technically, these values should be smaller than the apparent density
@@ -162,7 +174,9 @@ final class DisplayDeviceInfo {
                 && yDpi == other.yDpi
                 && flags == other.flags
                 && touch == other.touch
-                && rotation == other.rotation;
+                && rotation == other.rotation
+                && type == other.type
+                && Objects.equal(address, other.address);
     }
 
     @Override
@@ -181,6 +195,8 @@ final class DisplayDeviceInfo {
         flags = other.flags;
         touch = other.touch;
         rotation = other.rotation;
+        type = other.type;
+        address = other.address;
     }
 
     // For debugging purposes
@@ -191,6 +207,8 @@ final class DisplayDeviceInfo {
                 + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"
                 + ", touch " + touchToString(touch) + flagsToString(flags)
                 + ", rotation " + rotation
+                + ", type " + Display.typeToString(type)
+                + ", address " + address
                 + "}";
     }
 
index 919733d..7a104d7 100644 (file)
@@ -19,6 +19,7 @@ package com.android.server.display;
 import android.content.Context;
 import android.os.Handler;
 import android.util.DisplayMetrics;
+import android.view.Display;
 
 /**
  * Provides a fake default display for headless systems.
@@ -63,6 +64,7 @@ final class HeadlessDisplayAdapter extends DisplayAdapter {
                 mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
                         | DisplayDeviceInfo.FLAG_SECURE
                         | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
+                mInfo.type = Display.TYPE_BUILT_IN;
                 mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
             }
             return mInfo;
index fa56b83..b37d57f 100644 (file)
@@ -22,6 +22,7 @@ import android.os.IBinder;
 import android.os.Looper;
 import android.os.SystemProperties;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.DisplayEventReceiver;
 import android.view.Surface;
 import android.view.Surface.PhysicalDisplayInfo;
@@ -139,11 +140,13 @@ final class LocalDisplayAdapter extends DisplayAdapter {
                             com.android.internal.R.string.display_manager_built_in_display_name);
                     mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
                             | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+                    mInfo.type = Display.TYPE_BUILT_IN;
                     mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
                     mInfo.xDpi = mPhys.xDpi;
                     mInfo.yDpi = mPhys.yDpi;
                     mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
                 } else {
+                    mInfo.type = Display.TYPE_HDMI;
                     mInfo.name = getContext().getResources().getString(
                             com.android.internal.R.string.display_manager_hdmi_display_name);
                     mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
index aa62aee..1583137 100644 (file)
@@ -189,6 +189,8 @@ final class LogicalDisplay {
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SECURE) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_SECURE;
             }
+            mBaseDisplayInfo.type = deviceInfo.type;
+            mBaseDisplayInfo.address = deviceInfo.address;
             mBaseDisplayInfo.name = deviceInfo.name;
             mBaseDisplayInfo.appWidth = deviceInfo.width;
             mBaseDisplayInfo.appHeight = deviceInfo.height;
index c35fd98..36e9f74 100644 (file)
@@ -27,6 +27,7 @@ import android.os.IBinder;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+import android.view.Display;
 import android.view.Gravity;
 import android.view.Surface;
 
@@ -240,6 +241,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
                 mInfo.xDpi = mDensityDpi;
                 mInfo.yDpi = mDensityDpi;
                 mInfo.flags = 0;
+                mInfo.type = Display.TYPE_OVERLAY;
                 mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
             }
             return mInfo;
index 2ea83ee..45fff30 100644 (file)
@@ -39,6 +39,7 @@ import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
+import android.view.Display;
 import android.view.Surface;
 
 import java.io.PrintWriter;
@@ -293,9 +294,10 @@ final class WifiDisplayAdapter extends DisplayAdapter {
         float refreshRate = 60.0f; // TODO: get this for real
 
         String name = display.getFriendlyDisplayName();
+        String address = display.getDeviceAddress();
         IBinder displayToken = Surface.createDisplay(name, secure);
         mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
-                refreshRate, deviceFlags, surface);
+                refreshRate, deviceFlags, address, surface);
         sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
 
         scheduleUpdateNotificationLocked();
@@ -515,12 +517,13 @@ final class WifiDisplayAdapter extends DisplayAdapter {
         private final int mHeight;
         private final float mRefreshRate;
         private final int mFlags;
+        private final String mAddress;
 
         private Surface mSurface;
         private DisplayDeviceInfo mInfo;
 
         public WifiDisplayDevice(IBinder displayToken, String name,
-                int width, int height, float refreshRate, int flags,
+                int width, int height, float refreshRate, int flags, String address,
                 Surface surface) {
             super(WifiDisplayAdapter.this, displayToken);
             mName = name;
@@ -528,6 +531,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
             mHeight = height;
             mRefreshRate = refreshRate;
             mFlags = flags;
+            mAddress = address;
             mSurface = surface;
         }
 
@@ -555,6 +559,8 @@ final class WifiDisplayAdapter extends DisplayAdapter {
                 mInfo.height = mHeight;
                 mInfo.refreshRate = mRefreshRate;
                 mInfo.flags = mFlags;
+                mInfo.type = Display.TYPE_WIFI;
+                mInfo.address = mAddress;
                 mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
                 mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
             }