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)); 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 null /* windowContext */); 307 308 if (mVirtualDisplay != null) { 309 updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); 310 // Now create the ImageReader to supply a Surface to the new virtual display. 311 startImageReader(); 312 } else { 313 Log.w(TAG, "Virtual display id is null after createVirtualDisplay"); 314 updateDisplayId(INVALID_DISPLAY); 315 return; 316 } 317 } 318 319 Log.i(TAG, "VD created: " + mVirtualDisplay); 320 } 321 updateDisplayId(int displayId)322 private void updateDisplayId(int displayId) { 323 LocalServices.getService(ActivityTaskManagerInternal.class).setVr2dDisplayId(displayId); 324 mWindowManagerInternal.setVr2dDisplayId(displayId); 325 } 326 327 /** 328 * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout. 329 * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out 330 * of being enabled. This can happen sometimes with our 2D test app. 331 */ stopVirtualDisplay()332 private void stopVirtualDisplay() { 333 if (mStopVDRunnable == null) { 334 mStopVDRunnable = new Runnable() { 335 @Override 336 public void run() { 337 if (shouldRunVirtualDisplay()) { 338 Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on."); 339 } else { 340 Log.i(TAG, "Stopping Virtual Display"); 341 synchronized (mVdLock) { 342 updateDisplayId(INVALID_DISPLAY); 343 setSurfaceLocked(null); // clean up and release the surface first. 344 if (mVirtualDisplay != null) { 345 mVirtualDisplay.release(); 346 mVirtualDisplay = null; 347 } 348 stopImageReader(); 349 } 350 } 351 } 352 }; 353 } 354 355 mHandler.removeCallbacks(mStopVDRunnable); 356 mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS); 357 } 358 359 /** 360 * Set the surface to use with the virtual display. 361 * 362 * Code should be locked by {@link #mVdLock} before invoked. 363 * 364 * @param surface The Surface to set. 365 */ setSurfaceLocked(Surface surface)366 private void setSurfaceLocked(Surface surface) { 367 // Change the surface to either a valid surface or a null value. 368 if (mSurface != surface && (surface == null || surface.isValid())) { 369 Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface); 370 if (mVirtualDisplay != null) { 371 mVirtualDisplay.setSurface(surface); 372 } 373 if (mSurface != null) { 374 mSurface.release(); 375 } 376 mSurface = surface; 377 } 378 } 379 380 /** 381 * Starts an ImageReader as a do-nothing Surface. The virtual display will not get fully 382 * initialized within surface flinger unless it has a valid Surface associated with it. We use 383 * the ImageReader as the default valid Surface. 384 */ startImageReader()385 private void startImageReader() { 386 if (mImageReader == null) { 387 mImageReader = ImageReader.newInstance(mVirtualDisplayWidth, mVirtualDisplayHeight, 388 PixelFormat.RGBA_8888, 2 /* maxImages */); 389 Log.i(TAG, "VD startImageReader: res = " + mVirtualDisplayWidth + "X" + 390 mVirtualDisplayHeight + ", dpi = " + mVirtualDisplayDpi); 391 } 392 synchronized (mVdLock) { 393 setSurfaceLocked(mImageReader.getSurface()); 394 } 395 } 396 397 /** 398 * Cleans up the ImageReader. 399 */ stopImageReader()400 private void stopImageReader() { 401 if (mImageReader != null) { 402 mImageReader.close(); 403 mImageReader = null; 404 } 405 } 406 shouldRunVirtualDisplay()407 private boolean shouldRunVirtualDisplay() { 408 // Virtual Display should run whenever: 409 // * Virtual Display is allowed/enabled AND 410 // (1) BootsToVr is set indicating the device never leaves VR 411 // (2) VR (persistent) mode is enabled 412 // (3) VR mode is overridden to be enabled. 413 return mIsVirtualDisplayAllowed && 414 (mBootsToVr || mIsPersistentVrModeEnabled || mIsVrModeOverrideEnabled); 415 } 416 } 417