1 package com.android.server.vr; 2 3 import static android.view.Display.INVALID_DISPLAY; 4 5 import android.app.ActivityManagerInternal; 6 import android.app.Vr2dDisplayProperties; 7 import android.content.BroadcastReceiver; 8 import android.content.Context; 9 import android.content.Intent; 10 import android.content.IntentFilter; 11 import android.graphics.PixelFormat; 12 import android.hardware.display.DisplayManager; 13 import android.hardware.display.VirtualDisplay; 14 import android.hardware.display.VirtualDisplayConfig; 15 import android.media.ImageReader; 16 import android.os.Handler; 17 import android.os.RemoteException; 18 import android.service.vr.IPersistentVrStateCallbacks; 19 import android.service.vr.IVrManager; 20 import android.util.Log; 21 import android.view.Surface; 22 23 import com.android.server.LocalServices; 24 import com.android.server.wm.ActivityTaskManagerInternal; 25 import com.android.server.wm.WindowManagerInternal; 26 27 /** 28 * Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and 29 * render 2D app within a VR experience. For example, bringing up the 2D calculator app in VR. 30 */ 31 class Vr2dDisplay { 32 private final static String TAG = "Vr2dDisplay"; 33 private final static boolean DEBUG = false; 34 35 private int mVirtualDisplayHeight; 36 private int mVirtualDisplayWidth; 37 private int mVirtualDisplayDpi; 38 private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000; 39 private final static String UNIQUE_DISPLAY_ID = "277f1a09-b88d-4d1e-8716-796f114d080b"; 40 private final static String DISPLAY_NAME = "VR 2D Display"; 41 42 private final static String DEBUG_ACTION_SET_MODE = 43 "com.android.server.vr.Vr2dDisplay.SET_MODE"; 44 private final static String DEBUG_EXTRA_MODE_ON = 45 "com.android.server.vr.Vr2dDisplay.EXTRA_MODE_ON"; 46 private final static String DEBUG_ACTION_SET_SURFACE = 47 "com.android.server.vr.Vr2dDisplay.SET_SURFACE"; 48 private final static String DEBUG_EXTRA_SURFACE = 49 "com.android.server.vr.Vr2dDisplay.EXTRA_SURFACE"; 50 51 /** 52 * The default width of the VR virtual display 53 */ 54 public static final int DEFAULT_VIRTUAL_DISPLAY_WIDTH = 1400; 55 56 /** 57 * The default height of the VR virtual display 58 */ 59 public static final int DEFAULT_VIRTUAL_DISPLAY_HEIGHT = 1800; 60 61 /** 62 * The default height of the VR virtual dpi. 63 */ 64 public static final int DEFAULT_VIRTUAL_DISPLAY_DPI = 320; 65 66 /** 67 * The minimum height, width and dpi of VR virtual display. 68 */ 69 public static final int MIN_VR_DISPLAY_WIDTH = 1; 70 public static final int MIN_VR_DISPLAY_HEIGHT = 1; 71 public static final int MIN_VR_DISPLAY_DPI = 1; 72 73 private final ActivityManagerInternal mActivityManagerInternal; 74 private final WindowManagerInternal mWindowManagerInternal; 75 private final DisplayManager mDisplayManager; 76 private final IVrManager mVrManager; 77 private final Object mVdLock = new Object(); 78 private final Handler mHandler = new Handler(); 79 80 /** 81 * Callback implementation to receive changes to VrMode. 82 **/ 83 private final IPersistentVrStateCallbacks mVrStateCallbacks = 84 new IPersistentVrStateCallbacks.Stub() { 85 @Override 86 public void onPersistentVrStateChanged(boolean enabled) { 87 if (enabled != mIsPersistentVrModeEnabled) { 88 mIsPersistentVrModeEnabled = enabled; 89 updateVirtualDisplay(); 90 } 91 } 92 }; 93 94 private VirtualDisplay mVirtualDisplay; 95 private Surface mSurface; 96 private ImageReader mImageReader; 97 private Runnable mStopVDRunnable; 98 private boolean mIsVrModeOverrideEnabled; // debug override to set vr mode. 99 private boolean mIsVirtualDisplayAllowed = true; // Virtual-display feature toggle 100 private boolean mIsPersistentVrModeEnabled; // indicates we are in vr persistent mode. 101 private boolean mBootsToVr = false; // The device boots into VR (standalone VR device) 102 Vr2dDisplay(DisplayManager displayManager, ActivityManagerInternal activityManagerInternal, WindowManagerInternal windowManagerInternal, IVrManager vrManager)103 public Vr2dDisplay(DisplayManager displayManager, 104 ActivityManagerInternal activityManagerInternal, 105 WindowManagerInternal windowManagerInternal, IVrManager vrManager) { 106 mDisplayManager = displayManager; 107 mActivityManagerInternal = activityManagerInternal; 108 mWindowManagerInternal = windowManagerInternal; 109 mVrManager = vrManager; 110 mVirtualDisplayWidth = DEFAULT_VIRTUAL_DISPLAY_WIDTH; 111 mVirtualDisplayHeight = DEFAULT_VIRTUAL_DISPLAY_HEIGHT; 112 mVirtualDisplayDpi = DEFAULT_VIRTUAL_DISPLAY_DPI; 113 } 114 115 /** 116 * Initializes the compabilitiy display by listening to VR mode changes. 117 */ init(Context context, boolean bootsToVr)118 public void init(Context context, boolean bootsToVr) { 119 startVrModeListener(); 120 startDebugOnlyBroadcastReceiver(context); 121 mBootsToVr = bootsToVr; 122 if (mBootsToVr) { 123 // If we are booting into VR, we need to start the virtual display immediately. This 124 // ensures that the virtual display is up by the time Setup Wizard is started. 125 updateVirtualDisplay(); 126 } 127 } 128 129 /** 130 * Creates and Destroys the virtual display depending on the current state of VrMode. 131 */ updateVirtualDisplay()132 private void updateVirtualDisplay() { 133 if (DEBUG) { 134 Log.i(TAG, "isVrMode: " + mIsPersistentVrModeEnabled + ", override: " 135 + mIsVrModeOverrideEnabled + ", isAllowed: " + mIsVirtualDisplayAllowed 136 + ", bootsToVr: " + mBootsToVr); 137 } 138 139 if (shouldRunVirtualDisplay()) { 140 Log.i(TAG, "Attempting to start virtual display"); 141 // TODO: Consider not creating the display until ActivityManager needs one on 142 // which to display a 2D application. 143 startVirtualDisplay(); 144 } else { 145 // Stop virtual display to test exit condition 146 stopVirtualDisplay(); 147 } 148 } 149 150 /** 151 * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and 152 * set a custom Surface for the virtual display. This allows testing of the virtual display 153 * without going into full 3D. 154 * 155 * @param context The context. 156 */ startDebugOnlyBroadcastReceiver(Context context)157 private void startDebugOnlyBroadcastReceiver(Context context) { 158 if (DEBUG) { 159 IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE); 160 intentFilter.addAction(DEBUG_ACTION_SET_SURFACE); 161 162 context.registerReceiver(new BroadcastReceiver() { 163 @Override 164 public void onReceive(Context context, Intent intent) { 165 final String action = intent.getAction(); 166 if (DEBUG_ACTION_SET_MODE.equals(action)) { 167 mIsVrModeOverrideEnabled = 168 intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false); 169 updateVirtualDisplay(); 170 } else if (DEBUG_ACTION_SET_SURFACE.equals(action)) { 171 if (mVirtualDisplay != null) { 172 if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) { 173 setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE, android.view.Surface.class)); 174 } 175 } else { 176 Log.w(TAG, "Cannot set the surface because the VD is null."); 177 } 178 } 179 } 180 }, intentFilter, Context.RECEIVER_NOT_EXPORTED); 181 } 182 } 183 184 /** 185 * Starts listening to VrMode changes. 186 */ startVrModeListener()187 private void startVrModeListener() { 188 if (mVrManager != null) { 189 try { 190 mVrManager.registerPersistentVrStateListener(mVrStateCallbacks); 191 } catch (RemoteException e) { 192 Log.e(TAG, "Could not register VR State listener.", e); 193 } 194 } 195 } 196 197 /** 198 * Sets the resolution and DPI of the Vr2d virtual display used to display 199 * 2D applications in VR mode. 200 * 201 * <p>Requires {@link android.Manifest.permission#ACCESS_VR_MANAGER} permission.</p> 202 * 203 * @param displayProperties Properties of the virtual display for 2D applications 204 * in VR mode. 205 */ setVirtualDisplayProperties(Vr2dDisplayProperties displayProperties)206 public void setVirtualDisplayProperties(Vr2dDisplayProperties displayProperties) { 207 synchronized(mVdLock) { 208 if (DEBUG) { 209 Log.i(TAG, "VD setVirtualDisplayProperties: " + 210 displayProperties.toString()); 211 } 212 213 int width = displayProperties.getWidth(); 214 int height = displayProperties.getHeight(); 215 int dpi = displayProperties.getDpi(); 216 boolean resized = false; 217 218 if (width < MIN_VR_DISPLAY_WIDTH || height < MIN_VR_DISPLAY_HEIGHT || 219 dpi < MIN_VR_DISPLAY_DPI) { 220 Log.i(TAG, "Ignoring Width/Height/Dpi values of " + width + "," + height + "," 221 + dpi); 222 } else { 223 Log.i(TAG, "Setting width/height/dpi to " + width + "," + height + "," + dpi); 224 mVirtualDisplayWidth = width; 225 mVirtualDisplayHeight = height; 226 mVirtualDisplayDpi = dpi; 227 resized = true; 228 } 229 230 if ((displayProperties.getAddedFlags() & 231 Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) 232 == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) { 233 mIsVirtualDisplayAllowed = true; 234 } else if ((displayProperties.getRemovedFlags() & 235 Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) 236 == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) { 237 mIsVirtualDisplayAllowed = false; 238 } 239 240 if (mVirtualDisplay != null && resized && mIsVirtualDisplayAllowed) { 241 mVirtualDisplay.resize(mVirtualDisplayWidth, mVirtualDisplayHeight, 242 mVirtualDisplayDpi); 243 ImageReader oldImageReader = mImageReader; 244 mImageReader = null; 245 startImageReader(); 246 oldImageReader.close(); 247 } 248 249 // Start/Stop the virtual display in case the updates indicated that we should. 250 updateVirtualDisplay(); 251 } 252 } 253 254 /** 255 * Returns the virtual display ID if one currently exists, otherwise returns 256 * {@link INVALID_DISPLAY_ID}. 257 * 258 * @return The virtual display ID. 259 */ getVirtualDisplayId()260 public int getVirtualDisplayId() { 261 synchronized(mVdLock) { 262 if (mVirtualDisplay != null) { 263 int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId(); 264 if (DEBUG) { 265 Log.i(TAG, "VD id: " + virtualDisplayId); 266 } 267 return virtualDisplayId; 268 } 269 } 270 return INVALID_DISPLAY; 271 } 272 273 /** 274 * Starts the virtual display if one does not already exist. 275 */ startVirtualDisplay()276 private void startVirtualDisplay() { 277 if (DEBUG) { 278 Log.d(TAG, "Request to start VD, DM:" + mDisplayManager); 279 } 280 281 if (mDisplayManager == null) { 282 Log.w(TAG, "Cannot create virtual display because mDisplayManager == null"); 283 return; 284 } 285 286 synchronized (mVdLock) { 287 if (mVirtualDisplay != null) { 288 Log.i(TAG, "VD already exists, ignoring request"); 289 return; 290 } 291 292 int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; 293 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; 294 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; 295 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 296 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; 297 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; 298 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; 299 300 final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( 301 DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi); 302 builder.setUniqueId(UNIQUE_DISPLAY_ID); 303 builder.setFlags(flags); 304 mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */, 305 builder.build(), null /* callback */, null /* handler */); 306 307 if (mVirtualDisplay != null) { 308 updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); 309 // Now create the ImageReader to supply a Surface to the new virtual display. 310 startImageReader(); 311 } else { 312 Log.w(TAG, "Virtual display id is null after createVirtualDisplay"); 313 updateDisplayId(INVALID_DISPLAY); 314 return; 315 } 316 } 317 318 Log.i(TAG, "VD created: " + mVirtualDisplay); 319 } 320 updateDisplayId(int displayId)321 private void updateDisplayId(int displayId) { 322 LocalServices.getService(ActivityTaskManagerInternal.class).setVr2dDisplayId(displayId); 323 mWindowManagerInternal.setVr2dDisplayId(displayId); 324 } 325 326 /** 327 * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout. 328 * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out 329 * of being enabled. This can happen sometimes with our 2D test app. 330 */ stopVirtualDisplay()331 private void stopVirtualDisplay() { 332 if (mStopVDRunnable == null) { 333 mStopVDRunnable = new Runnable() { 334 @Override 335 public void run() { 336 if (shouldRunVirtualDisplay()) { 337 Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on."); 338 } else { 339 Log.i(TAG, "Stopping Virtual Display"); 340 synchronized (mVdLock) { 341 updateDisplayId(INVALID_DISPLAY); 342 setSurfaceLocked(null); // clean up and release the surface first. 343 if (mVirtualDisplay != null) { 344 mVirtualDisplay.release(); 345 mVirtualDisplay = null; 346 } 347 stopImageReader(); 348 } 349 } 350 } 351 }; 352 } 353 354 mHandler.removeCallbacks(mStopVDRunnable); 355 mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS); 356 } 357 358 /** 359 * Set the surface to use with the virtual display. 360 * 361 * Code should be locked by {@link #mVdLock} before invoked. 362 * 363 * @param surface The Surface to set. 364 */ setSurfaceLocked(Surface surface)365 private void setSurfaceLocked(Surface surface) { 366 // Change the surface to either a valid surface or a null value. 367 if (mSurface != surface && (surface == null || surface.isValid())) { 368 Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface); 369 if (mVirtualDisplay != null) { 370 mVirtualDisplay.setSurface(surface); 371 } 372 if (mSurface != null) { 373 mSurface.release(); 374 } 375 mSurface = surface; 376 } 377 } 378 379 /** 380 * Starts an ImageReader as a do-nothing Surface. The virtual display will not get fully 381 * initialized within surface flinger unless it has a valid Surface associated with it. We use 382 * the ImageReader as the default valid Surface. 383 */ startImageReader()384 private void startImageReader() { 385 if (mImageReader == null) { 386 mImageReader = ImageReader.newInstance(mVirtualDisplayWidth, mVirtualDisplayHeight, 387 PixelFormat.RGBA_8888, 2 /* maxImages */); 388 Log.i(TAG, "VD startImageReader: res = " + mVirtualDisplayWidth + "X" + 389 mVirtualDisplayHeight + ", dpi = " + mVirtualDisplayDpi); 390 } 391 synchronized (mVdLock) { 392 setSurfaceLocked(mImageReader.getSurface()); 393 } 394 } 395 396 /** 397 * Cleans up the ImageReader. 398 */ stopImageReader()399 private void stopImageReader() { 400 if (mImageReader != null) { 401 mImageReader.close(); 402 mImageReader = null; 403 } 404 } 405 shouldRunVirtualDisplay()406 private boolean shouldRunVirtualDisplay() { 407 // Virtual Display should run whenever: 408 // * Virtual Display is allowed/enabled AND 409 // (1) BootsToVr is set indicating the device never leaves VR 410 // (2) VR (persistent) mode is enabled 411 // (3) VR mode is overridden to be enabled. 412 return mIsVirtualDisplayAllowed && 413 (mBootsToVr || mIsPersistentVrModeEnabled || mIsVrModeOverrideEnabled); 414 } 415 } 416