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