| package com.android.server.vr; | |
| import static android.view.Display.INVALID_DISPLAY; | |
| import android.app.ActivityManagerInternal; | |
| import android.app.Vr2dDisplayProperties; | |
| import android.app.Service; | |
| 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.Build; | |
| import android.os.Handler; | |
| import android.os.IBinder; | |
| import android.os.Message; | |
| import android.os.RemoteException; | |
| import android.os.ServiceManager; | |
| import android.os.SystemProperties; | |
| import android.service.vr.IPersistentVrStateCallbacks; | |
| import android.service.vr.IVrManager; | |
| import android.util.Log; | |
| import android.view.Surface; | |
| import android.view.WindowManagerInternal; | |
| import com.android.server.vr.VrManagerService; | |
| /** | |
| * 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.getFlags() & 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; | |
| 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) { | |
| mActivityManagerInternal.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); | |
| } | |
| } |