Allow VR State Listeners with RESTRICTED_VR_ACCESS permission.

Extend the ability to use VrStateListeners to applications that
have RESTRICTED_VR_ACCESS permission.  Previously, it required
ACCESS_VR_MANAGER only.

Add VrStateCallback object and methods to VrManager to access the
functionality outside of frameworks.

Bug: 64360244
Test: Manual. Tested ability from VrCore.

Change-Id: I01f1c75b1ccd44a09364df84218168cf6caab322
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index b40c96c..363e20a 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -1,13 +1,18 @@
 package android.app;
 
-
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
-import android.content.ComponentName;
 import android.content.Context;
+import android.os.Handler;
 import android.os.RemoteException;
+import android.service.vr.IPersistentVrStateCallbacks;
 import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
+import android.util.ArrayMap;
+
+import java.util.Map;
 
 /**
  * Used to control aspects of a devices Virtual Reality (VR) capabilities.
@@ -16,7 +21,33 @@
 @SystemApi
 @SystemService(Context.VR_SERVICE)
 public class VrManager {
+
+    private static class CallbackEntry {
+        final IVrStateCallbacks mStateCallback = new IVrStateCallbacks.Stub() {
+            @Override
+            public void onVrStateChanged(boolean enabled) {
+                mHandler.post(() -> mCallback.onVrStateChanged(enabled));
+            }
+
+        };
+        final IPersistentVrStateCallbacks mPersistentStateCallback =
+                new IPersistentVrStateCallbacks.Stub() {
+            @Override
+            public void onPersistentVrStateChanged(boolean enabled) {
+                mHandler.post(() -> mCallback.onPersistentVrStateChanged(enabled));
+            }
+        };
+        final VrStateCallback mCallback;
+        final Handler mHandler;
+
+        CallbackEntry(VrStateCallback callback, Handler handler) {
+            mCallback = callback;
+            mHandler = handler;
+        }
+    }
+
     private final IVrManager mService;
+    private Map<VrStateCallback, CallbackEntry> mCallbackMap = new ArrayMap<>();
 
     /**
      * {@hide}
@@ -26,6 +57,84 @@
     }
 
     /**
+     * Registers a callback to be notified of changes to the VR Mode state.
+     *
+     * @param callback The callback to register.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    public void registerVrStateCallback(VrStateCallback callback, @NonNull Handler handler) {
+        if (callback == null || mCallbackMap.containsKey(callback)) {
+            return;
+        }
+
+        CallbackEntry entry = new CallbackEntry(callback, handler);
+        mCallbackMap.put(callback, entry);
+        try {
+            mService.registerListener(entry.mStateCallback);
+            mService.registerPersistentVrStateListener(entry.mPersistentStateCallback);
+        } catch (RemoteException e) {
+            try {
+                unregisterVrStateCallback(callback);
+            } catch (Exception ignore) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Deregisters VR State callbacks.
+     *
+     * @param callback The callback to deregister.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    public void unregisterVrStateCallback(VrStateCallback callback) {
+        CallbackEntry entry = mCallbackMap.remove(callback);
+        if (entry != null) {
+            try {
+                mService.unregisterListener(entry.mStateCallback);
+            } catch (RemoteException ignore) {
+                // Dont rethrow exceptions from requests to unregister.
+            }
+
+            try {
+                mService.unregisterPersistentVrStateListener(entry.mPersistentStateCallback);
+            } catch (RemoteException ignore) {
+                // Dont rethrow exceptions from requests to unregister.
+            }
+        }
+    }
+
+    /**
+     * Returns the current VrMode state.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+    public boolean getVrModeEnabled() {
+        try {
+            return mService.getVrModeState();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current VrMode state.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+    public boolean getPersistentVrModeEnabled() {
+        try {
+            return mService.getPersistentVrModeEnabled();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
      * Sets the persistent VR mode state of a device. When a device is in persistent VR mode it will
      * remain in VR mode even if the foreground does not specify Vr mode being enabled. Mainly used
      * by VR viewers to indicate that a device is placed in a VR viewer.
diff --git a/core/java/android/app/VrStateCallback.java b/core/java/android/app/VrStateCallback.java
new file mode 100644
index 0000000..742faa06
--- /dev/null
+++ b/core/java/android/app/VrStateCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+/**
+ * Listens to VR Mode state changes. Use with methods in {@link VrManager}.
+ *
+ * @hide
+ */
+public abstract class VrStateCallback {
+
+    /**
+     * Callback triggered when there is a change to Persistent VR State.
+     *
+     * @param enabled True when VR State is in persistent mode, false otherwise.
+     */
+    public void onPersistentVrStateChanged(boolean enabled) {}
+
+    /**
+     * Callback triggered when there is a change to Vr State.
+     *
+     * @param enabled True when VR State is in VR mode, false otherwise.
+     */
+    public void onVrStateChanged(boolean enabled) {}
+}
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index 9b37a65..c38fab1 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -59,6 +59,13 @@
     boolean getVrModeState();
 
     /**
+     * Returns the current Persistent VR mode state.
+     *
+     * @return {@code true} if Persistent VR mode is enabled.
+     */
+    boolean getPersistentVrModeEnabled();
+
+    /**
      * Sets the persistent VR mode state of a device. When a device is in persistent VR mode it will
      * remain in VR mode even if the foreground does not specify VR mode being enabled. Mainly used
      * by VR viewers to indicate that a device is placed in a VR viewer.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86c0b43..89bbec2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3512,6 +3512,11 @@
     <permission android:name="android.permission.ACCESS_VR_MANAGER"
             android:protectionLevel="signature" />
 
+    <!-- Required to access VR-Mode state and state change events via {android.app.VrStateCallback}
+         @hide -->
+    <permission android:name="android.permission.ACCESS_VR_STATE"
+        android:protectionLevel="signature|preinstalled" />
+
     <!-- Allows an application to whitelist tasks during lock task mode
          @hide <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"