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