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