| 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 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; |
| |
| // TODO: Go over these values and figure out what is best |
| 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_VR_DISPLAY_WIDTH = 1400; |
| |
| /** |
| * The default height of the VR virtual display |
| */ |
| public static final int DEFAULT_VR_DISPLAY_HEIGHT = 1800; |
| |
| /** |
| * The default height of the VR virtual dpi. |
| */ |
| public static final int DEFAULT_VR_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 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 != mIsVrModeEnabled) { |
| mIsVrModeEnabled = enabled; |
| updateVirtualDisplay(); |
| } |
| } |
| }; |
| |
| private VirtualDisplay mVirtualDisplay; |
| private Surface mSurface; |
| private ImageReader mImageReader; |
| private Runnable mStopVDRunnable; |
| private boolean mIsVrModeOverrideEnabled; |
| private boolean mIsVrModeEnabled; |
| |
| public Vr2dDisplay(DisplayManager displayManager, |
| ActivityManagerInternal activityManagerInternal, IVrManager vrManager) { |
| mDisplayManager = displayManager; |
| mActivityManagerInternal = activityManagerInternal; |
| mVrManager = vrManager; |
| mVirtualDisplayWidth = DEFAULT_VR_DISPLAY_WIDTH; |
| mVirtualDisplayHeight = DEFAULT_VR_DISPLAY_HEIGHT; |
| mVirtualDisplayDpi = DEFAULT_VR_DISPLAY_DPI; |
| } |
| |
| /** |
| * Initializes the compabilitiy display by listening to VR mode changes. |
| */ |
| public void init(Context context) { |
| startVrModeListener(); |
| startDebugOnlyBroadcastReceiver(context); |
| } |
| |
| /** |
| * Creates and Destroys the virtual display depending on the current state of VrMode. |
| */ |
| private void updateVirtualDisplay() { |
| if (DEBUG) { |
| Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", override: " + mIsVrModeOverrideEnabled); |
| } |
| |
| if (mIsVrModeEnabled || mIsVrModeOverrideEnabled) { |
| // 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 compatDisplayProperties Properties of the virtual display for 2D applications |
| * in VR mode. |
| */ |
| public void setVirtualDisplayProperties(Vr2dDisplayProperties compatDisplayProperties) { |
| synchronized(mVdLock) { |
| if (DEBUG) { |
| Log.i(TAG, "VD setVirtualDisplayProperties: res = " |
| + compatDisplayProperties.getWidth() + "X" |
| + compatDisplayProperties.getHeight() + ", dpi = " |
| + compatDisplayProperties.getDpi()); |
| } |
| |
| if (compatDisplayProperties.getWidth() < MIN_VR_DISPLAY_WIDTH || |
| compatDisplayProperties.getHeight() < MIN_VR_DISPLAY_HEIGHT || |
| compatDisplayProperties.getDpi() < MIN_VR_DISPLAY_DPI) { |
| throw new IllegalArgumentException ( |
| "Illegal argument: height, width, dpi cannot be negative. res = " |
| + compatDisplayProperties.getWidth() + "X" |
| + compatDisplayProperties.getHeight() |
| + ", dpi = " + compatDisplayProperties.getDpi()); |
| } |
| |
| mVirtualDisplayWidth = compatDisplayProperties.getWidth(); |
| mVirtualDisplayHeight = compatDisplayProperties.getHeight(); |
| mVirtualDisplayDpi = compatDisplayProperties.getDpi(); |
| |
| if (mVirtualDisplay != null) { |
| mVirtualDisplay.resize(mVirtualDisplayWidth, mVirtualDisplayHeight, |
| mVirtualDisplayDpi); |
| ImageReader oldImageReader = mImageReader; |
| mImageReader = null; |
| startImageReader(); |
| oldImageReader.close(); |
| } |
| } |
| } |
| |
| /** |
| * 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; |
| mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */, |
| DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi, |
| null /* surface */, flags, null /* callback */, null /* handler */, |
| UNIQUE_DISPLAY_ID); |
| |
| if (mVirtualDisplay != null) { |
| mActivityManagerInternal.setVr2dDisplayId( |
| 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"); |
| mActivityManagerInternal.setVr2dDisplayId(INVALID_DISPLAY); |
| return; |
| } |
| } |
| |
| Log.i(TAG, "VD created: " + mVirtualDisplay); |
| } |
| |
| /** |
| * 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 (mIsVrModeEnabled) { |
| Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on."); |
| } else { |
| Log.i(TAG, "Stopping Virtual Display"); |
| synchronized (mVdLock) { |
| mActivityManagerInternal.setVr2dDisplayId(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; |
| } |
| } |
| } |