1package com.android.server.vr; 2 3import static android.view.Display.INVALID_DISPLAY; 4 5import android.app.ActivityManagerInternal; 6import android.app.Vr2dDisplayProperties; 7import android.app.Service; 8import android.content.BroadcastReceiver; 9import android.content.Context; 10import android.content.Intent; 11import android.content.IntentFilter; 12import android.graphics.PixelFormat; 13import android.hardware.display.DisplayManager; 14import android.hardware.display.VirtualDisplay; 15import android.media.ImageReader; 16import android.os.Build; 17import android.os.Handler; 18import android.os.IBinder; 19import android.os.Message; 20import android.os.RemoteException; 21import android.os.ServiceManager; 22import android.os.SystemProperties; 23import android.service.vr.IPersistentVrStateCallbacks; 24import android.service.vr.IVrManager; 25import android.util.Log; 26import android.view.Surface; 27 28import com.android.server.vr.VrManagerService; 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 */ 34class Vr2dDisplay { 35 private final static String TAG = "Vr2dDisplay"; 36 private final static boolean DEBUG = false; 37 38 // TODO: Go over these values and figure out what is best 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_VR_DISPLAY_WIDTH = 1400; 59 60 /** 61 * The default height of the VR virtual display 62 */ 63 public static final int DEFAULT_VR_DISPLAY_HEIGHT = 1800; 64 65 /** 66 * The default height of the VR virtual dpi. 67 */ 68 public static final int DEFAULT_VR_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 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 != mIsVrModeEnabled) { 91 mIsVrModeEnabled = 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; 102 private boolean mIsVrModeEnabled; 103 104 public Vr2dDisplay(DisplayManager displayManager, 105 ActivityManagerInternal activityManagerInternal, IVrManager vrManager) { 106 mDisplayManager = displayManager; 107 mActivityManagerInternal = activityManagerInternal; 108 mVrManager = vrManager; 109 mVirtualDisplayWidth = DEFAULT_VR_DISPLAY_WIDTH; 110 mVirtualDisplayHeight = DEFAULT_VR_DISPLAY_HEIGHT; 111 mVirtualDisplayDpi = DEFAULT_VR_DISPLAY_DPI; 112 } 113 114 /** 115 * Initializes the compabilitiy display by listening to VR mode changes. 116 */ 117 public void init(Context context) { 118 startVrModeListener(); 119 startDebugOnlyBroadcastReceiver(context); 120 } 121 122 /** 123 * Creates and Destroys the virtual display depending on the current state of VrMode. 124 */ 125 private void updateVirtualDisplay() { 126 if (DEBUG) { 127 Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", override: " + mIsVrModeOverrideEnabled); 128 } 129 130 if (mIsVrModeEnabled || mIsVrModeOverrideEnabled) { 131 // TODO: Consider not creating the display until ActivityManager needs one on 132 // which to display a 2D application. 133 startVirtualDisplay(); 134 } else { 135 // Stop virtual display to test exit condition 136 stopVirtualDisplay(); 137 } 138 } 139 140 /** 141 * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and 142 * set a custom Surface for the virtual display. This allows testing of the virtual display 143 * without going into full 3D. 144 * 145 * @param context The context. 146 */ 147 private void startDebugOnlyBroadcastReceiver(Context context) { 148 if (DEBUG) { 149 IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE); 150 intentFilter.addAction(DEBUG_ACTION_SET_SURFACE); 151 152 context.registerReceiver(new BroadcastReceiver() { 153 @Override 154 public void onReceive(Context context, Intent intent) { 155 final String action = intent.getAction(); 156 if (DEBUG_ACTION_SET_MODE.equals(action)) { 157 mIsVrModeOverrideEnabled = 158 intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false); 159 updateVirtualDisplay(); 160 } else if (DEBUG_ACTION_SET_SURFACE.equals(action)) { 161 if (mVirtualDisplay != null) { 162 if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) { 163 setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE)); 164 } 165 } else { 166 Log.w(TAG, "Cannot set the surface because the VD is null."); 167 } 168 } 169 } 170 }, intentFilter); 171 } 172 } 173 174 /** 175 * Starts listening to VrMode changes. 176 */ 177 private void startVrModeListener() { 178 if (mVrManager != null) { 179 try { 180 mVrManager.registerPersistentVrStateListener(mVrStateCallbacks); 181 } catch (RemoteException e) { 182 Log.e(TAG, "Could not register VR State listener.", e); 183 } 184 } 185 } 186 187 /** 188 * Sets the resolution and DPI of the Vr2d virtual display used to display 189 * 2D applications in VR mode. 190 * 191 * <p>Requires {@link android.Manifest.permission#ACCESS_VR_MANAGER} permission.</p> 192 * 193 * @param compatDisplayProperties Properties of the virtual display for 2D applications 194 * in VR mode. 195 */ 196 public void setVirtualDisplayProperties(Vr2dDisplayProperties compatDisplayProperties) { 197 synchronized(mVdLock) { 198 if (DEBUG) { 199 Log.i(TAG, "VD setVirtualDisplayProperties: res = " 200 + compatDisplayProperties.getWidth() + "X" 201 + compatDisplayProperties.getHeight() + ", dpi = " 202 + compatDisplayProperties.getDpi()); 203 } 204 205 if (compatDisplayProperties.getWidth() < MIN_VR_DISPLAY_WIDTH || 206 compatDisplayProperties.getHeight() < MIN_VR_DISPLAY_HEIGHT || 207 compatDisplayProperties.getDpi() < MIN_VR_DISPLAY_DPI) { 208 throw new IllegalArgumentException ( 209 "Illegal argument: height, width, dpi cannot be negative. res = " 210 + compatDisplayProperties.getWidth() + "X" 211 + compatDisplayProperties.getHeight() 212 + ", dpi = " + compatDisplayProperties.getDpi()); 213 } 214 215 mVirtualDisplayWidth = compatDisplayProperties.getWidth(); 216 mVirtualDisplayHeight = compatDisplayProperties.getHeight(); 217 mVirtualDisplayDpi = compatDisplayProperties.getDpi(); 218 219 if (mVirtualDisplay != null) { 220 mVirtualDisplay.resize(mVirtualDisplayWidth, mVirtualDisplayHeight, 221 mVirtualDisplayDpi); 222 ImageReader oldImageReader = mImageReader; 223 mImageReader = null; 224 startImageReader(); 225 oldImageReader.close(); 226 } 227 } 228 } 229 230 /** 231 * Returns the virtual display ID if one currently exists, otherwise returns 232 * {@link INVALID_DISPLAY_ID}. 233 * 234 * @return The virtual display ID. 235 */ 236 public int getVirtualDisplayId() { 237 synchronized(mVdLock) { 238 if (mVirtualDisplay != null) { 239 int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId(); 240 if (DEBUG) { 241 Log.i(TAG, "VD id: " + virtualDisplayId); 242 } 243 return virtualDisplayId; 244 } 245 } 246 return INVALID_DISPLAY; 247 } 248 249 /** 250 * Starts the virtual display if one does not already exist. 251 */ 252 private void startVirtualDisplay() { 253 if (DEBUG) { 254 Log.d(TAG, "Request to start VD, DM:" + mDisplayManager); 255 } 256 257 if (mDisplayManager == null) { 258 Log.w(TAG, "Cannot create virtual display because mDisplayManager == null"); 259 return; 260 } 261 262 synchronized (mVdLock) { 263 if (mVirtualDisplay != null) { 264 Log.i(TAG, "VD already exists, ignoring request"); 265 return; 266 } 267 268 int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; 269 mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */, 270 DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi, 271 null /* surface */, flags, null /* callback */, null /* handler */, 272 UNIQUE_DISPLAY_ID); 273 274 if (mVirtualDisplay != null) { 275 mActivityManagerInternal.setVr2dDisplayId( 276 mVirtualDisplay.getDisplay().getDisplayId()); 277 // Now create the ImageReader to supply a Surface to the new virtual display. 278 startImageReader(); 279 } else { 280 Log.w(TAG, "Virtual display id is null after createVirtualDisplay"); 281 mActivityManagerInternal.setVr2dDisplayId(INVALID_DISPLAY); 282 return; 283 } 284 } 285 286 Log.i(TAG, "VD created: " + mVirtualDisplay); 287 } 288 289 /** 290 * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout. 291 * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out 292 * of being enabled. This can happen sometimes with our 2D test app. 293 */ 294 private void stopVirtualDisplay() { 295 if (mStopVDRunnable == null) { 296 mStopVDRunnable = new Runnable() { 297 @Override 298 public void run() { 299 if (mIsVrModeEnabled) { 300 Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on."); 301 } else { 302 Log.i(TAG, "Stopping Virtual Display"); 303 synchronized (mVdLock) { 304 mActivityManagerInternal.setVr2dDisplayId(INVALID_DISPLAY); 305 setSurfaceLocked(null); // clean up and release the surface first. 306 if (mVirtualDisplay != null) { 307 mVirtualDisplay.release(); 308 mVirtualDisplay = null; 309 } 310 stopImageReader(); 311 } 312 } 313 } 314 }; 315 } 316 317 mHandler.removeCallbacks(mStopVDRunnable); 318 mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS); 319 } 320 321 /** 322 * Set the surface to use with the virtual display. 323 * 324 * Code should be locked by {@link #mVdLock} before invoked. 325 * 326 * @param surface The Surface to set. 327 */ 328 private void setSurfaceLocked(Surface surface) { 329 // Change the surface to either a valid surface or a null value. 330 if (mSurface != surface && (surface == null || surface.isValid())) { 331 Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface); 332 if (mVirtualDisplay != null) { 333 mVirtualDisplay.setSurface(surface); 334 } 335 if (mSurface != null) { 336 mSurface.release(); 337 } 338 mSurface = surface; 339 } 340 } 341 342 /** 343 * Starts an ImageReader as a do-nothing Surface. The virtual display will not get fully 344 * initialized within surface flinger unless it has a valid Surface associated with it. We use 345 * the ImageReader as the default valid Surface. 346 */ 347 private void startImageReader() { 348 if (mImageReader == null) { 349 mImageReader = ImageReader.newInstance(mVirtualDisplayWidth, mVirtualDisplayHeight, 350 PixelFormat.RGBA_8888, 2 /* maxImages */); 351 Log.i(TAG, "VD startImageReader: res = " + mVirtualDisplayWidth + "X" + 352 mVirtualDisplayHeight + ", dpi = " + mVirtualDisplayDpi); 353 } 354 synchronized (mVdLock) { 355 setSurfaceLocked(mImageReader.getSurface()); 356 } 357 } 358 359 /** 360 * Cleans up the ImageReader. 361 */ 362 private void stopImageReader() { 363 if (mImageReader != null) { 364 mImageReader.close(); 365 mImageReader = null; 366 } 367 } 368} 369