Add a VR listener service.
Bug: 22855417
Bug: 26724891
Bug: 27364145
- Add an API for VrListenerService, which is bound/unbound
from the framework when the system VR mode changes.
- Allow only a single bound VrListenerService at a time.
- Monitor allowed VrListenerService implementations from
VrManagerService and evict services as needed when packages,
users, or settings change.
- Remove previous VR functionality in NotificationListenerService.
- Add component target to Activity#setVrMode to allow
explicit selection of the running VrListenerService from
the current VR activity.
Change-Id: I776335f4441be0e793d3126f2d16faf86a8c621a
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b87e9fa2..6b67b95 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6177,14 +6177,24 @@
/**
* Enable or disable virtual reality (VR) mode.
*
- * <p>VR mode is a hint to Android system services to switch to modes optimized for
- * high-performance stereoscopic rendering.</p>
+ * <p>VR mode is a hint to Android system services to switch to a mode optimized for
+ * high-performance stereoscopic rendering. This mode will be enabled while this Activity has
+ * focus.</p>
*
* @param enabled {@code true} to enable this mode.
+ * @param requestedComponent the name of the component to use as a
+ * {@link android.service.vr.VrListenerService} while VR mode is enabled.
+ *
+ * @throws android.content.pm.PackageManager.NameNotFoundException;
*/
- public void setVrMode(boolean enabled) {
+ public void setVrModeEnabled(boolean enabled, @NonNull ComponentName requestedComponent)
+ throws PackageManager.NameNotFoundException {
try {
- ActivityManagerNative.getDefault().setVrMode(mToken, enabled);
+ if (ActivityManagerNative.getDefault().setVrMode(mToken, enabled, requestedComponent)
+ != 0) {
+ throw new PackageManager.NameNotFoundException(
+ requestedComponent.flattenToString());
+ }
} catch (RemoteException e) {
// pass
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index a4e5b90..f64bf1d 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3371,6 +3371,15 @@
}
}
+ /** {@hide} */
+ public boolean isVrModePackageEnabled(ComponentName component) {
+ try {
+ return ActivityManagerNative.getDefault().isVrModePackageEnabled(component);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Perform a system dump of various state associated with the given application
* package name. This call blocks while the dump is being performed, so should
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index ff7f70d..6fbb430 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2902,8 +2902,18 @@
data.enforceInterface(IActivityManager.descriptor);
final IBinder token = data.readStrongBinder();
final boolean enable = data.readInt() == 1;
- setVrMode(token, enable);
+ final ComponentName packageName = ComponentName.CREATOR.createFromParcel(data);
+ int res = setVrMode(token, enable, packageName);
reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
+ case IS_VR_PACKAGE_ENABLED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final ComponentName packageName = ComponentName.CREATOR.createFromParcel(data);
+ boolean res = isVrModePackageEnabled(packageName);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
return true;
}
case IS_APP_FOREGROUND_TRANSACTION: {
@@ -6247,16 +6257,34 @@
return res;
}
- public void setVrMode(IBinder token, boolean enabled) throws RemoteException {
+ public int setVrMode(IBinder token, boolean enabled, ComponentName packageName)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
data.writeInt(enabled ? 1 : 0);
+ packageName.writeToParcel(data, 0);
mRemote.transact(SET_VR_MODE_TRANSACTION, data, reply, 0);
reply.readException();
+ int res = reply.readInt();
data.recycle();
reply.recycle();
+ return res;
+ }
+
+ public boolean isVrModePackageEnabled(ComponentName packageName)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ packageName.writeToParcel(data, 0);
+ mRemote.transact(IS_VR_PACKAGE_ENABLED_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res == 1;
}
@Override
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 70bff80..eadf497 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -33,6 +33,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.ProviderInfo;
import android.content.pm.UserInfo;
@@ -599,7 +600,10 @@
public void enterPictureInPicture(IBinder token) throws RemoteException;
- public void setVrMode(IBinder token, boolean enabled) throws RemoteException;
+ public int setVrMode(IBinder token, boolean enabled, ComponentName packageName)
+ throws RemoteException;
+
+ public boolean isVrModePackageEnabled(ComponentName packageName) throws RemoteException;
public boolean isAppForeground(int uid) throws RemoteException;
@@ -993,4 +997,5 @@
int SET_LENIENT_BACKGROUND_CHECK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+368;
int GET_MEMORY_TRIM_LEVEL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+369;
int RESIZE_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 370;
+ int IS_VR_PACKAGE_ENABLED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 371;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a40cf96..940ac48 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1254,6 +1254,19 @@
public static final String ACTION_SHOW_REMOTE_BUGREPORT_DIALOG
= "android.settings.SHOW_REMOTE_BUGREPORT_DIALOG";
+ /**
+ * Activity Action: Show VR listener settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @see android.service.vr.VrListenerService
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VR_LISTENER_SETTINGS
+ = "android.settings.VR_LISTENER_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -6041,6 +6054,14 @@
public static final String BRIGHTNESS_USE_TWILIGHT = "brightness_use_twilight";
/**
+ * Names of the service components that the current user has explicitly allowed to
+ * be a VR mode listener, separated by ':'.
+ *
+ * @hide
+ */
+ public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -6067,6 +6088,7 @@
BACKUP_AUTO_RESTORE,
ENABLED_ACCESSIBILITY_SERVICES,
ENABLED_NOTIFICATION_LISTENERS,
+ ENABLED_VR_LISTENERS,
ENABLED_INPUT_METHODS,
TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
TOUCH_EXPLORATION_ENABLED,
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 73a890f..b8f9812 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -69,16 +69,6 @@
* <action android:name="android.service.notification.NotificationListenerService" />
* </intent-filter>
* </service></pre>
- * <p> Typically, while enabled in user settings, this service will be bound on boot or when a
- * settings change occurs that could affect whether this service should run. However, for some
- * system usage modes, the you may instead specify that this service is instead bound and unbound
- * in response to mode changes by including a category in the intent filter. Currently
- * supported categories are:
- * <ul>
- * <li>{@link #CATEGORY_VR_NOTIFICATIONS} - this service is bound when an Activity has enabled
- * VR mode. {@see android.app.Activity#setVrMode(boolean)}.</li>
- * </ul>
- * </p>
*/
public abstract class NotificationListenerService extends Service {
// TAG = "NotificationListenerService[MySubclass]"
@@ -195,17 +185,6 @@
public static final String SERVICE_INTERFACE
= "android.service.notification.NotificationListenerService";
- /**
- * If this category is declared in the application manifest for a service of this type, this
- * service will be bound when VR mode is enabled, and unbound when VR mode is disabled rather
- * than the normal lifecycle for a notification service.
- *
- * {@see android.app.Activity#setVrMode(boolean)}
- */
- @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
- public static final String CATEGORY_VR_NOTIFICATIONS =
- "android.intent.category.vr.notifications";
-
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
diff --git a/core/java/android/service/vr/IVrListener.aidl b/core/java/android/service/vr/IVrListener.aidl
new file mode 100644
index 0000000..b7273ba
--- /dev/null
+++ b/core/java/android/service/vr/IVrListener.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2016, 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.service.vr;
+
+/** @hide */
+oneway interface IVrListener {
+
+}
\ No newline at end of file
diff --git a/core/java/android/service/vr/VrListenerService.java b/core/java/android/service/vr/VrListenerService.java
new file mode 100644
index 0000000..5f1f659
--- /dev/null
+++ b/core/java/android/service/vr/VrListenerService.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 2016 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.service.vr;
+
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.app.ActivityManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * A service that is bound from the system while running in virtual reality (VR) mode.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_VR_LISTENER_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * <service android:name=".VrListener"
+ * android:label="@string/service_name"
+ * android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.vr.VrListenerService" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ *
+ * <p>
+ * This service is bound when the system enters VR mode and is unbound when the system leaves VR
+ * mode.
+ * {@see android.app.Activity#setVrMode(boolean)}
+ * </p>
+ */
+public abstract class VrListenerService extends Service {
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.service.vr.VrListenerService";
+
+ /**
+ * @hide
+ */
+ public static class VrListenerBinder extends IVrListener.Stub {
+ }
+
+ private final VrListenerBinder mBinder = new VrListenerBinder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Check if the given package is available to be enabled/disabled in VR mode settings.
+ *
+ * @param context the {@link Context} to use for looking up the requested component.
+ * @param requestedComponent the name of the component that implements
+ * {@link android.service.vr.VrListenerService} to check.
+ *
+ * @return {@code true} if this package is enabled in settings.
+ */
+ public static final boolean isVrModePackageEnabled(@NonNull Context context,
+ @NonNull ComponentName requestedComponent) {
+ ActivityManager am = context.getSystemService(ActivityManager.class);
+ if (am == null) {
+ return false;
+ }
+ return am.isVrModePackageEnabled(requestedComponent);
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8f85d4a..a5136db 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2929,6 +2929,12 @@
<permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
android:protectionLevel="signature" />
+ <!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only
+ the system can bind to it.
+ <p>Protection level: signature -->
+ <permission android:name="android.permission.BIND_VR_LISTENER_SERVICE"
+ android:protectionLevel="signature" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d8efd63..ee4e665 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3135,6 +3135,8 @@
<!-- Label to show for a service that is running because it is observing
the user's notifications. -->
<string name="notification_listener_binding_label">Notification listener</string>
+ <!-- Label to show for a service that is running because the system is in VR mode. -->
+ <string name="vr_listener_binding_label">VR listener</string>
<!-- Label to show for a service that is running because it is providing conditions. -->
<string name="condition_provider_service_binding_label">Condition provider</string>
<!-- Label to show for a service that is running because it is observing and modifying the
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 15521e4..4c6f98c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1842,6 +1842,7 @@
<java-symbol type="string" name="low_internal_storage_view_text_no_boot" />
<java-symbol type="string" name="low_internal_storage_view_title" />
<java-symbol type="string" name="notification_listener_binding_label" />
+ <java-symbol type="string" name="vr_listener_binding_label" />
<java-symbol type="string" name="condition_provider_service_binding_label" />
<java-symbol type="string" name="notification_assistant_binding_label" />
<java-symbol type="string" name="report" />