content: /*
   * Copyright (C) 2012 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.app;
  
  import static android.content.Context.DISPLAY_SERVICE;
  import static android.content.Context.WINDOW_SERVICE;
  import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
  import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
  
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.content.res.Resources;
  import android.hardware.display.DisplayManager;
  import android.hardware.display.DisplayManager.DisplayListener;
  import android.os.Binder;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Message;
  import android.util.DisplayMetrics;
  import android.util.Log;
  import android.util.TypedValue;
  import android.view.ContextThemeWrapper;
  import android.view.Display;
  import android.view.Gravity;
  import android.view.Window;
  import android.view.WindowManager;
  import android.view.WindowManagerImpl;
  
  /**
   * Base class for presentations.
   * <p>
   * A presentation is a special kind of dialog whose purpose is to present
   * content on a secondary display.  A {@link Presentation} is associated with
   * the target {@link Display} at creation time and configures its context and
   * resource configuration according to the display's metrics.
   * </p><p>
   * Notably, the {@link Context} of a presentation is different from the context
   * of its containing {@link Activity}.  It is important to inflate the layout
   * of a presentation and load other resources using the presentation's own context
   * to ensure that assets of the correct size and density for the target display
   * are loaded.
   * </p><p>
   * A presentation is automatically canceled (see {@link Dialog#cancel()}) when
   * the display to which it is attached is removed.  An activity should take
   * care of pausing and resuming whatever content is playing within the presentation
   * 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>
   * <pre>
   * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
   * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
   * if (route != null) {
   *     Display presentationDisplay = route.getPresentationDisplay();
   *     if (presentationDisplay != null) {
   *         Presentation presentation = new MyPresentation(context, presentationDisplay);
   *         presentation.show();
   *     }
   * }</pre>
   * <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).  Creating a presentation on the main
   * display will result in {@link android.view.WindowManager.InvalidDisplayException} being thrown
   * when invoking {@link #show()}.
   * </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>
   * <pre>
   * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
   * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
   * if (presentationDisplays.length > 0) {
   *     // 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();
   * }</pre>
   * <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.
   */
  public class Presentation extends Dialog {
      private static final String TAG = "Presentation";
  
      private static final int MSG_CANCEL = 1;
  
      private final Display mDisplay;
      private final DisplayManager mDisplayManager;
      private final IBinder mToken = new Binder();
  
      /**
       * Creates a new presentation that is attached to the specified display
       * using the default theme.
       *
       * @param outerContext The context of the application that is showing the presentation.
       * The presentation will create its own context (see {@link #getContext()}) based
       * on this context and information about the associated display.
       * @param display The display to which the presentation should be attached.
       */
      public Presentation(Context outerContext, Display display) {
          this(outerContext, display, 0);
      }
  
      /**
       * Creates a new presentation that is attached to the specified display
       * using the optionally specified theme.
       *
       * @param outerContext The context of the application that is showing the presentation.
       * The presentation will create its own context (see {@link #getContext()}) based
       * on this context and information about the associated display.
       * @param display The display to which the presentation should be attached.
       * @param theme A style resource describing the theme to use for the window.
       * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
       * Style and Theme Resources</a> for more information about defining and using
       * styles.  This theme is applied on top of the current theme in
       * <var>outerContext</var>.  If 0, the default presentation theme will be used.
       */
      public Presentation(Context outerContext, Display display, int theme) {
          super(createPresentationContext(outerContext, display, theme), theme, false);
  
          mDisplay = display;
          mDisplayManager = (DisplayManager)getContext().getSystemService(DISPLAY_SERVICE);
  
          final int windowType =
                  (display.getFlags() & Display.FLAG_PRIVATE) != 0 ? TYPE_PRIVATE_PRESENTATION
                          : TYPE_PRESENTATION;
  
          final Window w = getWindow();
          final WindowManager.LayoutParams attr = w.getAttributes();
          attr.token = mToken;
          w.setAttributes(attr);
          w.setGravity(Gravity.FILL);
          w.setType(windowType);
          setCanceledOnTouchOutside(false);
      }
  
      /**
       * Gets the {@link Display} that this presentation appears on.
       *
       * @return The display.
       */
      public Display getDisplay() {
          return mDisplay;
      }
  
      /**
       * Gets the {@link Resources} that should be used to inflate the layout of this presentation.
       * This resources object has been configured according to the metrics of the
       * display that the presentation appears on.
       *
       * @return The presentation resources object.
       */
      public Resources getResources() {
          return getContext().getResources();
      }
  
      @Override
      protected void onStart() {
          super.onStart();
          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
          // dismiss the presentation immediately.  This case is expected
          // to be rare but surprising, so we'll write a log message about it.
          if (!isConfigurationStillValid()) {
              Log.i(TAG, "Presentation is being dismissed because the "
                      + "display metrics have changed since it was created.");
              mHandler.sendEmptyMessage(MSG_CANCEL);
          }
      }
  
      @Override
      protected void onStop() {
          mDisplayManager.unregisterDisplayListener(mDisplayListener);
          super.onStop();
      }
  
      /**
       * Inherited from {@link Dialog#show}. Will throw
       * {@link android.view.WindowManager.InvalidDisplayException} if the specified secondary
       * {@link Display} can't be found or if it does not have {@link Display#FLAG_PRESENTATION} set.
       */
      @Override
      public void show() {
          super.show();
      }
  
      /**
       * Called by the system when the {@link Display} to which the presentation
       * is attached has been removed.
       *
       * The system automatically calls {@link #cancel} to dismiss the presentation
       * after sending this event.
       *
       * @see #getDisplay
       */
      public void onDisplayRemoved() {
      }
  
      /**
       * Called by the system when the properties of the {@link Display} to which
       * the presentation is attached have changed.
       *
       * If the display metrics have changed (for example, if the display has been
       * resized or rotated), then the system automatically calls
       * {@link #cancel} to dismiss the presentation.
       *
       * @see #getDisplay
       */
      public void onDisplayChanged() {
      }
  
      private void handleDisplayRemoved() {
          onDisplayRemoved();
          cancel();
      }
  
      private void handleDisplayChanged() {
          onDisplayChanged();
  
          // We currently do not support configuration changes for presentations
          // (although we could add that feature with a bit more work).
          // If the display metrics have changed in any way then the current configuration
          // is invalid and the application must recreate the presentation to get
          // a new context.
          if (!isConfigurationStillValid()) {
              Log.i(TAG, "Presentation is being dismissed because the "
                      + "display metrics have changed since it was created.");
              cancel();
          }
      }
  
      private boolean isConfigurationStillValid() {
          DisplayMetrics dm = new DisplayMetrics();
          mDisplay.getMetrics(dm);
          return dm.equalsPhysical(getResources().getDisplayMetrics());
      }
  
      @UnsupportedAppUsage
      private static Context createPresentationContext(
              Context outerContext, Display display, int theme) {
          if (outerContext == null) {
              throw new IllegalArgumentException("outerContext must not be null");
          }
          if (display == null) {
              throw new IllegalArgumentException("display must not be null");
          }
  
          Context displayContext = outerContext.createDisplayContext(display);
          if (theme == 0) {
              TypedValue outValue = new TypedValue();
              displayContext.getTheme().resolveAttribute(
                      com.android.internal.R.attr.presentationTheme, outValue, true);
              theme = outValue.resourceId;
          }
  
          // Derive the display's window manager from the outer window manager.
          // We do this because the outer window manager have some extra information
          // such as the parent window, which is important if the presentation uses
          // an application window type.
          final WindowManagerImpl outerWindowManager =
                  (WindowManagerImpl)outerContext.getSystemService(WINDOW_SERVICE);
          final WindowManagerImpl displayWindowManager =
                  outerWindowManager.createPresentationWindowManager(displayContext);
          return new ContextThemeWrapper(displayContext, theme) {
              @Override
              public Object getSystemService(String name) {
                  if (WINDOW_SERVICE.equals(name)) {
                      return displayWindowManager;
                  }
                  return super.getSystemService(name);
              }
          };
      }
  
      private final DisplayListener mDisplayListener = new DisplayListener() {
          @Override
          public void onDisplayAdded(int displayId) {
          }
  
          @Override
          public void onDisplayRemoved(int displayId) {
              if (displayId == mDisplay.getDisplayId()) {
                  handleDisplayRemoved();
              }
          }
  
          @Override
          public void onDisplayChanged(int displayId) {
              if (displayId == mDisplay.getDisplayId()) {
                  handleDisplayChanged();
              }
          }
      };
  
      private final Handler mHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case MSG_CANCEL:
                      cancel();
                      break;
              }
          }
      };
  }
  