content: package com.android.server.vr;
  
  import static android.view.Display.INVALID_DISPLAY;
  
  import android.app.ActivityManagerInternal;
  import android.app.Vr2dDisplayProperties;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.graphics.PixelFormat;
  import android.hardware.display.DisplayManager;
  import android.hardware.display.VirtualDisplay;
  import android.media.ImageReader;
  import android.os.Handler;
  import android.os.RemoteException;
  import android.service.vr.IPersistentVrStateCallbacks;
  import android.service.vr.IVrManager;
  import android.util.Log;
  import android.view.Surface;
  
  import com.android.server.LocalServices;
  import com.android.server.wm.ActivityTaskManagerInternal;
  import com.android.server.wm.WindowManagerInternal;
  
  /**
   * Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and
   * render 2D app within a VR experience. For example, bringing up the 2D calculator app in VR.
   */
  class Vr2dDisplay {
      private final static String TAG = "Vr2dDisplay";
      private final static boolean DEBUG = false;
  
      private int mVirtualDisplayHeight;
      private int mVirtualDisplayWidth;
      private int mVirtualDisplayDpi;
      private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000;
      private final static String UNIQUE_DISPLAY_ID = "277f1a09-b88d-4d1e-8716-796f114d080b";
      private final static String DISPLAY_NAME = "VR 2D Display";
  
      private final static String DEBUG_ACTION_SET_MODE =
              "com.android.server.vr.Vr2dDisplay.SET_MODE";
      private final static String DEBUG_EXTRA_MODE_ON =
              "com.android.server.vr.Vr2dDisplay.EXTRA_MODE_ON";
      private final static String DEBUG_ACTION_SET_SURFACE =
              "com.android.server.vr.Vr2dDisplay.SET_SURFACE";
      private final static String DEBUG_EXTRA_SURFACE =
              "com.android.server.vr.Vr2dDisplay.EXTRA_SURFACE";
  
      /**
       * The default width of the VR virtual display
       */
      public static final int DEFAULT_VIRTUAL_DISPLAY_WIDTH = 1400;
  
      /**
       * The default height of the VR virtual display
       */
      public static final int DEFAULT_VIRTUAL_DISPLAY_HEIGHT = 1800;
  
      /**
       * The default height of the VR virtual dpi.
       */
      public static final int DEFAULT_VIRTUAL_DISPLAY_DPI = 320;
  
      /**
       * The minimum height, width and dpi of VR virtual display.
       */
      public static final int MIN_VR_DISPLAY_WIDTH = 1;
      public static final int MIN_VR_DISPLAY_HEIGHT = 1;
      public static final int MIN_VR_DISPLAY_DPI = 1;
  
      private final ActivityManagerInternal mActivityManagerInternal;
      private final WindowManagerInternal mWindowManagerInternal;
      private final DisplayManager mDisplayManager;
      private final IVrManager mVrManager;
      private final Object mVdLock = new Object();
      private final Handler mHandler = new Handler();
  
      /**
       * Callback implementation to receive changes to VrMode.
       **/
      private final IPersistentVrStateCallbacks mVrStateCallbacks =
              new IPersistentVrStateCallbacks.Stub() {
          @Override
          public void onPersistentVrStateChanged(boolean enabled) {
              if (enabled != mIsPersistentVrModeEnabled) {
                  mIsPersistentVrModeEnabled = enabled;
                  updateVirtualDisplay();
              }
          }
      };
  
      private VirtualDisplay mVirtualDisplay;
      private Surface mSurface;
      private ImageReader mImageReader;
      private Runnable mStopVDRunnable;
      private boolean mIsVrModeOverrideEnabled;  // debug override to set vr mode.
      private boolean mIsVirtualDisplayAllowed = true;  // Virtual-display feature toggle
      private boolean mIsPersistentVrModeEnabled;  // indicates we are in vr persistent mode.
      private boolean mBootsToVr = false;  // The device boots into VR (standalone VR device)
  
      public Vr2dDisplay(DisplayManager displayManager,
             ActivityManagerInternal activityManagerInternal,
             WindowManagerInternal windowManagerInternal, IVrManager vrManager) {
          mDisplayManager = displayManager;
          mActivityManagerInternal = activityManagerInternal;
          mWindowManagerInternal = windowManagerInternal;
          mVrManager = vrManager;
          mVirtualDisplayWidth = DEFAULT_VIRTUAL_DISPLAY_WIDTH;
          mVirtualDisplayHeight = DEFAULT_VIRTUAL_DISPLAY_HEIGHT;
          mVirtualDisplayDpi = DEFAULT_VIRTUAL_DISPLAY_DPI;
      }
  
      /**
       * Initializes the compabilitiy display by listening to VR mode changes.
       */
      public void init(Context context, boolean bootsToVr) {
          startVrModeListener();
          startDebugOnlyBroadcastReceiver(context);
          mBootsToVr = bootsToVr;
          if (mBootsToVr) {
            // If we are booting into VR, we need to start the virtual display immediately. This
            // ensures that the virtual display is up by the time Setup Wizard is started.
            updateVirtualDisplay();
          }
      }
  
      /**
       * Creates and Destroys the virtual display depending on the current state of VrMode.
       */
      private void updateVirtualDisplay() {
          if (DEBUG) {
              Log.i(TAG, "isVrMode: " + mIsPersistentVrModeEnabled + ", override: "
                      + mIsVrModeOverrideEnabled + ", isAllowed: " + mIsVirtualDisplayAllowed
                      + ", bootsToVr: " + mBootsToVr);
          }
  
          if (shouldRunVirtualDisplay()) {
              Log.i(TAG, "Attempting to start virtual display");
              // TODO: Consider not creating the display until ActivityManager needs one on
              // which to display a 2D application.
              startVirtualDisplay();
          } else {
              // Stop virtual display to test exit condition
              stopVirtualDisplay();
          }
      }
  
      /**
       * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and
       * set a custom Surface for the virtual display.  This allows testing of the virtual display
       * without going into full 3D.
       *
       * @param context The context.
       */
      private void startDebugOnlyBroadcastReceiver(Context context) {
          if (DEBUG) {
              IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE);
              intentFilter.addAction(DEBUG_ACTION_SET_SURFACE);
  
              context.registerReceiver(new BroadcastReceiver() {
                  @Override
                  public void onReceive(Context context, Intent intent) {
                      final String action = intent.getAction();
                      if (DEBUG_ACTION_SET_MODE.equals(action)) {
                          mIsVrModeOverrideEnabled =
                                  intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false);
                          updateVirtualDisplay();
                      } else if (DEBUG_ACTION_SET_SURFACE.equals(action)) {
                          if (mVirtualDisplay != null) {
                              if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) {
                                  setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE));
                              }
                          } else {
                              Log.w(TAG, "Cannot set the surface because the VD is null.");
                          }
                      }
                  }
              }, intentFilter);
          }
      }
  
      /**
       * Starts listening to VrMode changes.
       */
      private void startVrModeListener() {
          if (mVrManager != null) {
              try {
                  mVrManager.registerPersistentVrStateListener(mVrStateCallbacks);
              } catch (RemoteException e) {
                  Log.e(TAG, "Could not register VR State listener.", e);
              }
          }
      }
  
      /**
       * Sets the resolution and DPI of the Vr2d virtual display used to display
       * 2D applications in VR mode.
       *
       * <p>Requires {@link android.Manifest.permission#ACCESS_VR_MANAGER} permission.</p>
       *
       * @param displayProperties Properties of the virtual display for 2D applications
       * in VR mode.
       */
      public void setVirtualDisplayProperties(Vr2dDisplayProperties displayProperties) {
          synchronized(mVdLock) {
              if (DEBUG) {
                  Log.i(TAG, "VD setVirtualDisplayProperties: " +
                          displayProperties.toString());
              }
  
              int width = displayProperties.getWidth();
              int height = displayProperties.getHeight();
              int dpi = displayProperties.getDpi();
              boolean resized = false;
  
              if (width < MIN_VR_DISPLAY_WIDTH || height < MIN_VR_DISPLAY_HEIGHT ||
                      dpi < MIN_VR_DISPLAY_DPI) {
                  Log.i(TAG, "Ignoring Width/Height/Dpi values of " + width + "," + height + ","
                          + dpi);
              } else {
                  Log.i(TAG, "Setting width/height/dpi to " + width + "," + height + "," + dpi);
                  mVirtualDisplayWidth = width;
                  mVirtualDisplayHeight = height;
                  mVirtualDisplayDpi = dpi;
                  resized = true;
              }
  
              if ((displayProperties.getAddedFlags() &
                      Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED)
                      == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) {
                  mIsVirtualDisplayAllowed = true;
              } else if ((displayProperties.getRemovedFlags() &
                      Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED)
                      == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) {
                  mIsVirtualDisplayAllowed = false;
              }
  
              if (mVirtualDisplay != null && resized && mIsVirtualDisplayAllowed) {
                  mVirtualDisplay.resize(mVirtualDisplayWidth, mVirtualDisplayHeight,
                      mVirtualDisplayDpi);
                  ImageReader oldImageReader = mImageReader;
                  mImageReader = null;
                  startImageReader();
                  oldImageReader.close();
              }
  
              // Start/Stop the virtual display in case the updates indicated that we should.
              updateVirtualDisplay();
          }
      }
  
      /**
       * Returns the virtual display ID if one currently exists, otherwise returns
       * {@link INVALID_DISPLAY_ID}.
       *
       * @return The virtual display ID.
       */
      public int getVirtualDisplayId() {
          synchronized(mVdLock) {
              if (mVirtualDisplay != null) {
                  int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
                  if (DEBUG) {
                      Log.i(TAG, "VD id: " + virtualDisplayId);
                  }
                  return virtualDisplayId;
              }
          }
          return INVALID_DISPLAY;
      }
  
      /**
       * Starts the virtual display if one does not already exist.
       */
      private void startVirtualDisplay() {
          if (DEBUG) {
              Log.d(TAG, "Request to start VD, DM:" + mDisplayManager);
          }
  
          if (mDisplayManager == null) {
              Log.w(TAG, "Cannot create virtual display because mDisplayManager == null");
              return;
          }
  
          synchronized (mVdLock) {
              if (mVirtualDisplay != null) {
                  Log.i(TAG, "VD already exists, ignoring request");
                  return;
              }
  
              int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
              flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
              flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
              flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
              flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
              flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
  
              mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
                      DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
                      null /* surface */, flags, null /* callback */, null /* handler */,
                      UNIQUE_DISPLAY_ID);
  
              if (mVirtualDisplay != null) {
                  updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
                  // Now create the ImageReader to supply a Surface to the new virtual display.
                  startImageReader();
              } else {
                  Log.w(TAG, "Virtual display id is null after createVirtualDisplay");
                  updateDisplayId(INVALID_DISPLAY);
                  return;
              }
          }
  
          Log.i(TAG, "VD created: " + mVirtualDisplay);
      }
  
      private void updateDisplayId(int displayId) {
          LocalServices.getService(ActivityTaskManagerInternal.class).setVr2dDisplayId(displayId);
          mWindowManagerInternal.setVr2dDisplayId(displayId);
      }
  
      /**
       * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout.
       * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out
       * of being enabled. This can happen sometimes with our 2D test app.
       */
      private void stopVirtualDisplay() {
          if (mStopVDRunnable == null) {
             mStopVDRunnable = new Runnable() {
                 @Override
                 public void run() {
                      if (shouldRunVirtualDisplay()) {
                          Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on.");
                      } else {
                          Log.i(TAG, "Stopping Virtual Display");
                          synchronized (mVdLock) {
                              updateDisplayId(INVALID_DISPLAY);
                              setSurfaceLocked(null); // clean up and release the surface first.
                              if (mVirtualDisplay != null) {
                                  mVirtualDisplay.release();
                                  mVirtualDisplay = null;
                              }
                              stopImageReader();
                          }
                      }
                 }
             };
          }
  
          mHandler.removeCallbacks(mStopVDRunnable);
          mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS);
      }
  
      /**
       * Set the surface to use with the virtual display.
       *
       * Code should be locked by {@link #mVdLock} before invoked.
       *
       * @param surface The Surface to set.
       */
      private void setSurfaceLocked(Surface surface) {
          // Change the surface to either a valid surface or a null value.
          if (mSurface != surface && (surface == null || surface.isValid())) {
              Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface);
              if (mVirtualDisplay != null) {
                  mVirtualDisplay.setSurface(surface);
              }
              if (mSurface != null) {
                  mSurface.release();
              }
              mSurface = surface;
          }
      }
  
      /**
       * Starts an ImageReader as a do-nothing Surface.  The virtual display will not get fully
       * initialized within surface flinger unless it has a valid Surface associated with it. We use
       * the ImageReader as the default valid Surface.
       */
      private void startImageReader() {
          if (mImageReader == null) {
              mImageReader = ImageReader.newInstance(mVirtualDisplayWidth, mVirtualDisplayHeight,
                  PixelFormat.RGBA_8888, 2 /* maxImages */);
              Log.i(TAG, "VD startImageReader: res = " + mVirtualDisplayWidth + "X" +
                      mVirtualDisplayHeight + ", dpi = " + mVirtualDisplayDpi);
          }
          synchronized (mVdLock) {
              setSurfaceLocked(mImageReader.getSurface());
          }
      }
  
      /**
       * Cleans up the ImageReader.
       */
      private void stopImageReader() {
          if (mImageReader != null) {
              mImageReader.close();
              mImageReader = null;
          }
      }
  
      private boolean shouldRunVirtualDisplay() {
          // Virtual Display should run whenever:
          // * Virtual Display is allowed/enabled AND
          // (1) BootsToVr is set indicating the device never leaves VR
          // (2) VR (persistent) mode is enabled
          // (3) VR mode is overridden to be enabled.
          return mIsVirtualDisplayAllowed &&
                  (mBootsToVr || mIsPersistentVrModeEnabled || mIsVrModeOverrideEnabled);
      }
  }
  