Add a framework service tracking VR mode state.
- Implement a "VR mode" that may be enabled by a focused Activity.
- Add a system service that tracks the current VR mode state and notifies
other core framework services of mode changes.
- Extend NotificationListenerService to allow the bind/unbind lifecycle
of specified listeners to be triggered by system events.
Bug: 22855417
Bug: 25479708
Change-Id: I1ac8692bbb5521bb6c7cfb9d2b56b98b720f8568
diff --git a/api/current.txt b/api/current.txt
index 622a8b7..557afcd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3538,6 +3538,7 @@
method public deprecated void setTitleColor(int);
method public void setVisible(boolean);
method public final void setVolumeControlStream(int);
+ method public void setVrMode(boolean);
method public boolean shouldShowRequestPermissionRationale(java.lang.String);
method public boolean shouldUpRecreateTask(android.content.Intent);
method public boolean showAssist(android.os.Bundle);
@@ -8989,6 +8990,7 @@
field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
+ field public static final int FLAG_ENABLE_VR_MODE = 32768; // 0x8000
field public static final int FLAG_EXCLUDE_FROM_RECENTS = 32; // 0x20
field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100
field public static final int FLAG_FINISH_ON_TASK_LAUNCH = 2; // 0x2
@@ -9552,6 +9554,7 @@
field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+ field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -33243,6 +33246,7 @@
method public final void requestInterruptionFilter(int);
method public final void requestListenerHints(int);
method public final void setNotificationsShown(java.lang.String[]);
+ field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications";
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
diff --git a/api/system-current.txt b/api/system-current.txt
index e51b7d5..57ae432 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3643,6 +3643,7 @@
method public deprecated void setTitleColor(int);
method public void setVisible(boolean);
method public final void setVolumeControlStream(int);
+ method public void setVrMode(boolean);
method public boolean shouldShowRequestPermissionRationale(java.lang.String);
method public boolean shouldUpRecreateTask(android.content.Intent);
method public boolean showAssist(android.os.Bundle);
@@ -9257,6 +9258,7 @@
field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
+ field public static final int FLAG_ENABLE_VR_MODE = 32768; // 0x8000
field public static final int FLAG_EXCLUDE_FROM_RECENTS = 32; // 0x20
field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100
field public static final int FLAG_FINISH_ON_TASK_LAUNCH = 2; // 0x2
@@ -9869,6 +9871,7 @@
field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+ field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -35389,6 +35392,7 @@
method public final void setNotificationsShown(java.lang.String[]);
method public final void setOnNotificationPostedTrim(int);
method public void unregisterAsSystemService() throws android.os.RemoteException;
+ field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications";
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
diff --git a/api/test-current.txt b/api/test-current.txt
index 2377a9b..e506cfc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3538,6 +3538,7 @@
method public deprecated void setTitleColor(int);
method public void setVisible(boolean);
method public final void setVolumeControlStream(int);
+ method public void setVrMode(boolean);
method public boolean shouldShowRequestPermissionRationale(java.lang.String);
method public boolean shouldUpRecreateTask(android.content.Intent);
method public boolean showAssist(android.os.Bundle);
@@ -8989,6 +8990,7 @@
field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
+ field public static final int FLAG_ENABLE_VR_MODE = 32768; // 0x8000
field public static final int FLAG_EXCLUDE_FROM_RECENTS = 32; // 0x20
field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100
field public static final int FLAG_FINISH_ON_TASK_LAUNCH = 2; // 0x2
@@ -9552,6 +9554,7 @@
field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+ field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -33245,6 +33248,7 @@
method public final void requestInterruptionFilter(int);
method public final void requestListenerHints(int);
method public final void setNotificationsShown(java.lang.String[]);
+ field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications";
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 571bc6e..68cf72a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6022,6 +6022,22 @@
}
/**
+ * 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>
+ *
+ * @param enabled {@code true} to enable this mode.
+ */
+ public void setVrMode(boolean enabled) {
+ try {
+ ActivityManagerNative.getDefault().setVrMode(mToken, enabled);
+ } catch (RemoteException e) {
+ // pass
+ }
+ }
+
+ /**
* Start an action mode of the default type {@link ActionMode#TYPE_PRIMARY}.
*
* @param callback Callback that will manage lifecycle events for this action mode
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d724823..1b08273 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2766,6 +2766,14 @@
reply.writeNoException();
return true;
}
+ case SET_VR_MODE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final IBinder token = data.readStrongBinder();
+ final boolean enable = data.readInt() == 1;
+ setVrMode(token, enable);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -5896,6 +5904,18 @@
return res;
}
+ public void setVrMode(IBinder token, boolean enabled) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ data.writeInt(enabled ? 1 : 0);
+ mRemote.transact(SET_VR_MODE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
@Override
public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index f91a0be..0ecf223 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -552,6 +552,8 @@
public void enterPictureInPictureMode(IBinder token) throws RemoteException;
+ public void setVrMode(IBinder token, boolean enabled) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -917,4 +919,5 @@
int IN_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 353;
int KILL_PACKAGE_DEPENDENTS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 354;
int ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 355;
+ int SET_VR_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 356;
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0cb0e9f..326735e 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -276,6 +276,12 @@
*/
public static final int FLAG_RESUME_WHILE_PAUSING = 0x4000;
/**
+ * Bit in {@link #flags} indicating that this activity should be run with VR mode enabled.
+ *
+ * {@see android.app.Activity#setVrMode(boolean)}.
+ */
+ public static final int FLAG_ENABLE_VR_MODE = 0x8000;
+ /**
* @hide Bit in {@link #flags}: If set, this component will only be seen
* by the system user. Only works with broadcast receivers. Set from the
* android.R.attr#systemUserOnly attribute.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3e7deb9..38242fb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1838,6 +1838,16 @@
public static final String FEATURE_MIDI = "android.software.midi";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device implements a an optimized mode for virtual reality (VR) applications that handles
+ * stereoscopic rendering of notifications, and may potentially also include optimizations to
+ * reduce latency in the graphics, display, and sensor stacks. Presence of this feature
+ * also indicates that the VrCore library is included on this device.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VR_MODE = "android.software.vr.mode";
+
+ /**
* Action to external storage service to clean out removed apps.
* @hide
*/
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 232d4fe..1e62edc 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -60,6 +60,16 @@
* <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]"
@@ -162,6 +172,17 @@
= "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";
+
+ /**
* Implement this method to learn about new notifications as they are posted by apps.
*
* @param sbn A data structure encapsulating the original {@link android.app.Notification}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b805c10..70f3c05 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -53,6 +53,7 @@
import com.android.server.firewall.IntentFirewall;
import com.android.server.pm.Installer;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.AppTransition;
import com.android.server.wm.WindowManagerService;
@@ -1440,6 +1441,7 @@
static final int IDLE_UIDS_MSG = 60;
static final int SYSTEM_USER_UNLOCK_MSG = 61;
static final int LOG_STACK_STATE = 62;
+ static final int VR_MODE_CHANGE_MSG = 63;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2154,6 +2156,10 @@
mStackSupervisor.logStackState();
}
} break;
+ case VR_MODE_CHANGE_MSG: {
+ VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
+ vrService.setVrMode(msg.arg1 != 0);
+ } break;
}
}
};
@@ -2776,6 +2782,7 @@
mWindowManager.setFocusedApp(r.appToken, true);
}
applyUpdateLockStateLocked(r);
+ applyUpdateVrModeLocked(r);
if (mFocusedActivity.userId != mLastFocusedUserId) {
mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
mHandler.obtainMessage(
@@ -2872,6 +2879,11 @@
mHandler.obtainMessage(IMMERSIVE_MODE_LOCK_MSG, (nextState) ? 1 : 0, 0, r));
}
+ final void applyUpdateVrModeLocked(ActivityRecord r) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(VR_MODE_CHANGE_MSG, (r.isVrActivity) ? 1 : 0, 0));
+ }
+
final void showAskCompatModeDialogLocked(ActivityRecord r) {
Message msg = Message.obtain();
msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
@@ -11712,6 +11724,26 @@
}
}
+ @Override
+ public void setVrMode(IBinder token, boolean enabled) {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
+ throw new UnsupportedOperationException("VR mode not supported on this device!");
+ }
+
+ synchronized(this) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ throw new IllegalArgumentException();
+ }
+ r.isVrActivity = enabled;
+
+ // Update associated state if this activity is currently focused
+ if (r == mFocusedActivity) {
+ applyUpdateVrModeLocked(r);
+ }
+ }
+ }
+
public boolean isTopActivityImmersive() {
enforceNotIsolatedCaller("startActivity");
synchronized (this) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d215fc6..b64aa0e 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -173,6 +173,7 @@
boolean forceNewConfig; // force re-create with new config next time
int launchCount; // count of launches since last state
long lastLaunchTime; // time of last lauch of this activity
+ boolean isVrActivity; // is the activity running in VR mode?
ArrayList<ActivityContainer> mChildContainers = new ArrayList<ActivityContainer>();
String stringName; // for caching of toString().
@@ -309,6 +310,7 @@
pw.print(" forceNewConfig="); pw.println(forceNewConfig);
pw.print(prefix); pw.print("mActivityType=");
pw.println(activityTypeToString(mActivityType));
+ pw.print(prefix); pw.print("vrMode="); pw.println(isVrActivity);
if (displayStartTime != 0 || startTime != 0) {
pw.print(prefix); pw.print("displayStartTime=");
if (displayStartTime == 0) pw.print("0");
@@ -637,6 +639,7 @@
}
immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0;
+ isVrActivity = (aInfo.flags & ActivityInfo.FLAG_ENABLE_VR_MODE) != 0;
} else {
realActivity = null;
taskAffinity = null;
@@ -648,6 +651,7 @@
noDisplay = false;
mActivityType = APPLICATION_ACTIVITY_TYPE;
immersive = false;
+ isVrActivity = false;
}
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index d5773690..b7662da 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -42,6 +42,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -53,6 +54,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
@@ -93,6 +95,8 @@
// List of packages in restored setting across all mUserProfiles, for quick
// filtering upon package updates.
private ArraySet<String> mRestoredPackages = new ArraySet<>();
+ // State of current service categories
+ private ArrayMap<String, Boolean> mCategoryEnabled = new ArrayMap<>();
// Kept to de-dupe user change events (experienced after boot, when we receive a settings and a
@@ -262,6 +266,46 @@
}
}
+ public void setCategoryState(String category, boolean enabled) {
+ synchronized (mMutex) {
+ final Boolean previous = mCategoryEnabled.put(category, enabled);
+ if (!(previous == null || previous != enabled)) {
+ return;
+ }
+
+ // State changed
+ if (DEBUG) {
+ Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "category " + category);
+ }
+
+ final int[] userIds = mUserProfiles.getCurrentProfileIds();
+ for (int userId : userIds) {
+ final Set<ComponentName> componentNames = queryPackageForServices(null,
+ userId, category);
+
+ // Disallow services not enabled in Settings
+ final ArraySet<ComponentName> userComponents =
+ loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+ if (userComponents == null) {
+ componentNames.clear();
+ } else {
+ componentNames.retainAll(userComponents);
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Components for category " + category + ": " + componentNames);
+ }
+ for (ComponentName c : componentNames) {
+ if (enabled) {
+ registerServiceLocked(c, userId);
+ } else {
+ unregisterServiceLocked(c, userId);
+ }
+ }
+ }
+
+ }
+ }
private void rebuildRestoredPackages() {
mRestoredPackages.clear();
@@ -283,9 +327,9 @@
int userId) {
final ContentResolver cr = mContext.getContentResolver();
String settingValue = Settings.Secure.getStringForUser(
- cr,
- settingName,
- userId);
+ cr,
+ settingName,
+ userId);
if (TextUtils.isEmpty(settingValue))
return null;
String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
@@ -314,10 +358,10 @@
TextUtils.join(ENABLED_SERVICES_SEPARATOR, componentNames);
final ContentResolver cr = mContext.getContentResolver();
Settings.Secure.putStringForUser(
- cr,
- settingName,
- value,
- userId);
+ cr,
+ settingName,
+ value,
+ userId);
}
/**
@@ -333,12 +377,20 @@
}
protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
+ return queryPackageForServices(packageName, userId, null);
+ }
+
+ protected Set<ComponentName> queryPackageForServices(String packageName, int userId,
+ String category) {
Set<ComponentName> installed = new ArraySet<>();
final PackageManager pm = mContext.getPackageManager();
Intent queryIntent = new Intent(mConfig.serviceInterface);
if (!TextUtils.isEmpty(packageName)) {
queryIntent.setPackage(packageName);
}
+ if (category != null) {
+ queryIntent.addCategory(category);
+ }
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
queryIntent,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
@@ -353,9 +405,9 @@
ComponentName component = new ComponentName(info.packageName, info.name);
if (!mConfig.bindPermission.equals(info.permission)) {
Slog.w(TAG, "Skipping " + getCaption() + " service "
- + info.packageName + "/" + info.name
- + ": it does not require the permission "
- + mConfig.bindPermission);
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + mConfig.bindPermission);
continue;
}
installed.add(component);
@@ -449,6 +501,16 @@
}
final ArrayList<ComponentName> add = new ArrayList<>(userComponents);
+
+ // Remove components from disabled categories so that those services aren't run.
+ for (Entry<String, Boolean> e : mCategoryEnabled.entrySet()) {
+ if (!e.getValue()) {
+ Set<ComponentName> c = queryPackageForServices(null, userIds[i],
+ e.getKey());
+ add.removeAll(c);
+ }
+ }
+
toAdd.put(userIds[i], add);
newEnabled.addAll(userComponents);
@@ -488,93 +550,97 @@
* Version of registerService that takes the name of a service component to bind to.
*/
private void registerService(final ComponentName name, final int userid) {
+ synchronized (mMutex) {
+ registerServiceLocked(name, userid);
+ }
+ }
+
+ private void registerServiceLocked(final ComponentName name, final int userid) {
if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
- synchronized (mMutex) {
- final String servicesBindingTag = name.toString() + "/" + userid;
- if (mServicesBinding.contains(servicesBindingTag)) {
- // stop registering this thing already! we're working on it
- return;
- }
- mServicesBinding.add(servicesBindingTag);
+ final String servicesBindingTag = name.toString() + "/" + userid;
+ if (mServicesBinding.contains(servicesBindingTag)) {
+ // stop registering this thing already! we're working on it
+ return;
+ }
+ mServicesBinding.add(servicesBindingTag);
- final int N = mServices.size();
- for (int i = N - 1; i >= 0; i--) {
- final ManagedServiceInfo info = mServices.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- // cut old connections
- if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": "
- + info.service);
- removeServiceLocked(i);
- if (info.connection != null) {
- mContext.unbindService(info.connection);
- }
+ final int N = mServices.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ // cut old connections
+ if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": "
+ + info.service);
+ removeServiceLocked(i);
+ if (info.connection != null) {
+ mContext.unbindService(info.connection);
}
}
+ }
- Intent intent = new Intent(mConfig.serviceInterface);
- intent.setComponent(name);
+ Intent intent = new Intent(mConfig.serviceInterface);
+ intent.setComponent(name);
- intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
+ intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
- final PendingIntent pendingIntent = PendingIntent.getActivity(
- mContext, 0, new Intent(mConfig.settingsAction), 0);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext, 0, new Intent(mConfig.settingsAction), 0);
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
- ApplicationInfo appInfo = null;
- try {
- appInfo = mContext.getPackageManager().getApplicationInfo(
- name.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- // Ignore if the package doesn't exist we won't be able to bind to the service.
- }
- final int targetSdkVersion =
- appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mContext.getPackageManager().getApplicationInfo(
+ name.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ // Ignore if the package doesn't exist we won't be able to bind to the service.
+ }
+ final int targetSdkVersion =
+ appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
- try {
- if (DEBUG) Slog.v(TAG, "binding: " + intent);
- ServiceConnection serviceConnection = new ServiceConnection() {
- IInterface mService;
+ try {
+ if (DEBUG) Slog.v(TAG, "binding: " + intent);
+ ServiceConnection serviceConnection = new ServiceConnection() {
+ IInterface mService;
- @Override
- public void onServiceConnected(ComponentName name, IBinder binder) {
- boolean added = false;
- ManagedServiceInfo info = null;
- synchronized (mMutex) {
- mServicesBinding.remove(servicesBindingTag);
- try {
- mService = asInterface(binder);
- info = newServiceInfo(mService, name,
- userid, false /*isSystem*/, this, targetSdkVersion);
- binder.linkToDeath(info, 0);
- added = mServices.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- if (added) {
- onServiceAdded(info);
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ boolean added = false;
+ ManagedServiceInfo info = null;
+ synchronized (mMutex) {
+ mServicesBinding.remove(servicesBindingTag);
+ try {
+ mService = asInterface(binder);
+ info = newServiceInfo(mService, name,
+ userid, false /*isSystem*/, this, targetSdkVersion);
+ binder.linkToDeath(info, 0);
+ added = mServices.add(info);
+ } catch (RemoteException e) {
+ // already dead
}
}
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Slog.v(TAG, getCaption() + " connection lost: " + name);
+ if (added) {
+ onServiceAdded(info);
}
- };
- if (!mContext.bindServiceAsUser(intent,
- serviceConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- new UserHandle(userid))) {
- mServicesBinding.remove(servicesBindingTag);
- Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
- return;
}
- } catch (SecurityException ex) {
- Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Slog.v(TAG, getCaption() + " connection lost: " + name);
+ }
+ };
+ if (!mContext.bindServiceAsUser(intent,
+ serviceConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ new UserHandle(userid))) {
+ mServicesBinding.remove(servicesBindingTag);
+ Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
return;
}
+ } catch (SecurityException ex) {
+ Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
+ return;
}
}
@@ -583,20 +649,24 @@
*/
private void unregisterService(ComponentName name, int userid) {
synchronized (mMutex) {
- final int N = mServices.size();
- for (int i = N - 1; i >= 0; i--) {
- final ManagedServiceInfo info = mServices.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- removeServiceLocked(i);
- if (info.connection != null) {
- try {
- mContext.unbindService(info.connection);
- } catch (IllegalArgumentException ex) {
- // something happened to the service: we think we have a connection
- // but it's bogus.
- Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
- }
+ unregisterServiceLocked(name, userid);
+ }
+ }
+
+ private void unregisterServiceLocked(ComponentName name, int userid) {
+ final int N = mServices.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ removeServiceLocked(i);
+ if (info.connection != null) {
+ try {
+ mContext.unbindService(info.connection);
+ } catch (IllegalArgumentException ex) {
+ // something happened to the service: we think we have a connection
+ // but it's bogus.
+ Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bb07e9d..3a8f041 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -124,6 +124,9 @@
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vr.VrManagerInternal;
+import com.android.server.vr.VrStateListener;
+
import libcore.io.IoUtils;
import org.json.JSONArray;
import org.json.JSONException;
@@ -206,6 +209,8 @@
AudioManagerInternal mAudioManagerInternal;
StatusBarManagerInternal mStatusBar;
Vibrator mVibrator;
+ private VrManagerInternal mVrManagerInternal;
+ private final NotificationVrListener mVrListener = new NotificationVrListener();
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -816,6 +821,14 @@
}
}
+ private final class NotificationVrListener extends VrStateListener {
+ @Override
+ public void onVrStateChanged(final boolean enabled) {
+ mListeners.setCategoryState(NotificationListenerService.CATEGORY_VR_NOTIFICATIONS,
+ enabled);
+ }
+ }
+
private SettingsObserver mSettingsObserver;
private ZenModeHelper mZenModeHelper;
@@ -1004,6 +1017,8 @@
// Grab our optional AudioService
mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
+ mVrManagerInternal = getLocalService(VrManagerInternal.class);
+ mVrManagerInternal.registerListener(mVrListener);
mZenModeHelper.onSystemReady();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
new file mode 100644
index 0000000..42db364
--- /dev/null
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 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 com.android.server.vr;
+
+/**
+ * VR mode local system service interface.
+ *
+ * @hide Only for use within system server.
+ */
+public abstract class VrManagerInternal {
+
+ /**
+ * Return current VR mode state.
+ *
+ * @return {@code true} if VR mode is enabled.
+ */
+ public abstract boolean isInVrMode();
+
+ /**
+ * Set the current VR mode state.
+ *
+ * @param enabled {@code true} to enable VR mode.
+ */
+ public abstract void setVrMode(boolean enabled);
+
+ /**
+ * Add a listener for VR mode state changes.
+ * <p>
+ * This listener will immediately be called with the current VR mode state.
+ * </p>
+ * @param listener the listener instance to add.
+ */
+ public abstract void registerListener(VrStateListener listener);
+
+ /**
+ * Remove the listener from the current set of listeners.
+ *
+ * @param listener the listener to remove.
+ */
+ public abstract void unregisterListener(VrStateListener listener);
+
+}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
new file mode 100644
index 0000000..9a55e7f
--- /dev/null
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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 com.android.server.vr;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+
+/**
+ * Service tracking whether VR mode is active, and notifying listening system services of state
+ * changes.
+ *
+ * {@hide}
+ */
+public class VrManagerService extends SystemService {
+
+ public static final boolean DEBUG = false;
+ public static final String TAG = "VrManagerService";
+
+ private final Object mLock = new Object();
+ private boolean mVrModeEnabled = false;
+ private ArraySet<VrStateListener> mListeners = new ArraySet<>();
+
+ private final class LocalService extends VrManagerInternal {
+ @Override
+ public boolean isInVrMode() {
+ return VrManagerService.this.getVrMode();
+ }
+
+ @Override
+ public void setVrMode(boolean enabled) {
+ VrManagerService.this.setVrMode(enabled);
+ }
+
+ @Override
+ public void registerListener(VrStateListener listener) {
+ VrManagerService.this.addListener(listener);
+ }
+
+ @Override
+ public void unregisterListener(VrStateListener listener) {
+ VrManagerService.this.removeListener(listener);
+ }
+ }
+
+ public VrManagerService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishLocalService(VrManagerInternal.class, new LocalService());
+ }
+
+ private void addListener(VrStateListener listener) {
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ private void removeListener(VrStateListener listener) {
+ synchronized (mLock) {
+ mListeners.remove(listener);
+ }
+ }
+
+ private void setVrMode(boolean enabled) {
+ synchronized (mLock) {
+ if (mVrModeEnabled != enabled) {
+ mVrModeEnabled = enabled;
+ if (DEBUG) Slog.d(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
+ onVrModeChangedLocked();
+ }
+ }
+ }
+
+ private boolean getVrMode() {
+ synchronized (mLock) {
+ return mVrModeEnabled;
+ }
+ }
+
+ /**
+ * Notify system services of VR mode change.
+ */
+ private void onVrModeChangedLocked() {
+ for (VrStateListener l : mListeners) {
+ l.onVrStateChanged(mVrModeEnabled);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vr/VrStateListener.java b/services/core/java/com/android/server/vr/VrStateListener.java
new file mode 100644
index 0000000..b8af4b2
--- /dev/null
+++ b/services/core/java/com/android/server/vr/VrStateListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 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 com.android.server.vr;
+
+/**
+ * Listener for state changes in VrManagerService,
+ */
+public abstract class VrStateListener {
+
+ /**
+ * Called when the VR mode state changes.
+ *
+ * @param enabled {@code true} if VR mode is enabled.
+ */
+ public abstract void onVrStateChanged(boolean enabled);
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fb5f21a..3db8376 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -89,14 +89,13 @@
import com.android.server.tv.TvInputManagerService;
import com.android.server.twilight.TwilightService;
import com.android.server.usage.UsageStatsService;
-import com.android.server.usb.UsbService;
+import com.android.server.vr.VrManagerService;
import com.android.server.wallpaper.WallpaperManagerService;
import com.android.server.webkit.WebViewUpdateService;
import com.android.server.wm.WindowManagerService;
import dalvik.system.VMRuntime;
-import java.io.File;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
@@ -447,6 +446,7 @@
ConsumerIrService consumerIr = null;
MmsServiceBroker mmsService = null;
EntropyMixer entropyMixer = null;
+ VrManagerService vrManagerService = null;
boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
@@ -532,6 +532,10 @@
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ traceBeginAndSlog("StartVrManagerService");
+ mSystemServiceManager.startService(VrManagerService.class);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
mActivityManagerService.setWindowManager(wm);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());