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