Allow setting a persistent VR thread.
Bug: 36215076
Test: adb shell am instrument -w -e class \
android.os.SetPersistentVrThreadTest \
com.android.frameworks.coretests/\
android.support.test.runner.AndroidJUnitRunner
cts-tradefed run cts -m CtsVrTestCases -t android.vr.cts.VrSetFIFOThreadTest
Change-Id: If2f3a5f1c8593c74ac35964f68dcbe75b4da472e
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 043e0ab..8a0af9a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4060,6 +4060,10 @@
* thread can be a VR thread in a process at a time, and that thread may be subject to
* restrictions on the amount of time it can run.
*
+ * If persistent VR mode is set, whatever thread has been granted aggressive scheduling via this
+ * method will return to normal operation, and calling this method will do nothing while
+ * persistent VR mode is enabled.
+ *
* To reset the VR thread for an application, a tid of 0 can be passed.
*
* @see android.os.Process#myTid()
@@ -4074,6 +4078,31 @@
}
/**
+ * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads that persist
+ * beyond a single process. It requires holding the
+ * {@link android.Manifest.permission#RESTRICTED_VR_ACCESS} permission. Only one thread can be a
+ * persistent VR thread at a time, and that thread may be subject to restrictions on the amount
+ * of time it can run. Calling this method will disable aggressive scheduling for non-persistent
+ * VR threads set via {@link #setVrThread}. If persistent VR mode is disabled then the
+ * persistent VR thread loses its new scheduling priority; this method must be called again to
+ * set the persistent thread.
+ *
+ * To reset the persistent VR thread, a tid of 0 can be passed.
+ *
+ * @see android.os.Process#myTid()
+ * @param tid tid of the VR thread
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.RESTRICTED_VR_ACCESS)
+ public static void setPersistentVrThread(int tid) {
+ try {
+ getService().setPersistentVrThread(tid);
+ } catch (RemoteException e) {
+ // pass
+ }
+ }
+
+ /**
* The AppTask allows you to manage your own application's tasks.
* See {@link android.app.ActivityManager#getAppTasks()}
*/
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 77edaea..f5e0c38 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -605,6 +605,7 @@
ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
+ void setPersistentVrThread(int tid);
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 7b8c229..5669189 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -115,6 +115,9 @@
<!-- accessibility test permissions -->
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />
+ <!-- vr test permissions -->
+ <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
+
<application android:theme="@style/Theme" android:supportsRtl="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
new file mode 100644
index 0000000..920988b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.os;
+
+import android.app.ActivityManager;
+import android.app.VrManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * Tests ActivityManager#setPersistentVrThread and ActivityManager#setVrThread's
+ * interaction with persistent VR mode.
+ */
+public class SetPersistentVrThreadTest extends ActivityInstrumentationTestCase2<TestVrActivity> {
+ private TestVrActivity mActivity;
+ private ActivityManager mActivityManager;
+ private VrManager mVrManager;
+ private Context mContext;
+ private String mOldVrListener;
+ private ComponentName mRequestedComponent;
+ private static final int SCHED_OTHER = 0;
+ private static final int SCHED_FIFO = 1;
+ private static final int SCHED_RESET_ON_FORK = 0x40000000;
+ public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+ private static final String TAG = "VrSetPersistentFIFOThreadTest";
+
+ public SetPersistentVrThreadTest() {
+ super(TestVrActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mVrManager = (VrManager) mContext.getSystemService(Context.VR_SERVICE);
+
+ mRequestedComponent = new ComponentName(mContext,
+ TestVrActivity.TestVrListenerService.class);
+ mOldVrListener = Settings.Secure.getString(mContext.getContentResolver(),
+ ENABLED_VR_LISTENERS);
+ Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+ mRequestedComponent.flattenToString());
+ mActivity = getActivity();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ setPersistentVrModeEnabled(false);
+ } catch (Throwable e) {
+ // pass
+ }
+ Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+ mOldVrListener);
+ super.tearDown();
+ }
+
+ private void setPersistentVrModeEnabled(boolean enable) throws Throwable {
+ mVrManager.setPersistentVrModeEnabled(enable);
+ // Allow the system time to send out callbacks for persistent VR mode.
+ Thread.sleep(200);
+ }
+
+ @SmallTest
+ public void testSetPersistentVrThreadAPISuccess() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+
+ int vr_thread = 0, policy = 0;
+ vr_thread = Process.myTid();
+
+ setPersistentVrModeEnabled(true);
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+ // Check that the thread loses priority when persistent mode is disabled.
+ setPersistentVrModeEnabled(false);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+
+ // Check that disabling VR mode when in persistent mode does not affect the persistent
+ // thread.
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ Thread.sleep(200);
+ setPersistentVrModeEnabled(true);
+ Thread.sleep(200);
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+ mActivity.setVrModeEnabled(false, mRequestedComponent);
+ Thread.sleep(200);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+ setPersistentVrModeEnabled(false);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ }
+
+ @SmallTest
+ public void testSetPersistentVrThreadAPIFailure() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+ int vr_thread = 0, policy = 0;
+ vr_thread = Process.myTid();
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ }
+
+ @SmallTest
+ public void testSetVrThreadAPIFailsInPersistentMode() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+ int vr_thread = 0, policy = 0;
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ vr_thread = Process.myTid();
+
+ setPersistentVrModeEnabled(true);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ setPersistentVrModeEnabled(false);
+
+ // When not in persistent mode the API works again.
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+ // The thread loses priority when persistent mode is disabled.
+ setPersistentVrModeEnabled(true);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ setPersistentVrModeEnabled(false);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b40e709..1649c89 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -368,6 +368,7 @@
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vr.PersistentVrStateListener;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.WindowManagerService;
@@ -593,7 +594,70 @@
// default action automatically. Important for devices without direct input
// devices.
private boolean mShowDialogs = true;
- private boolean mInVrMode = false;
+ // VR state flags.
+ static final int NON_VR_MODE = 0;
+ static final int VR_MODE = 1;
+ static final int PERSISTENT_VR_MODE = 2;
+ private int mVrState = NON_VR_MODE;
+ private int mTopAppVrThreadTid = 0;
+ private int mPersistentVrThreadTid = 0;
+ final PersistentVrStateListener mPersistentVrModeListener =
+ new PersistentVrStateListener() {
+ @Override
+ public void onPersistentVrStateChanged(boolean enabled) {
+ synchronized(ActivityManagerService.this) {
+ // There are 4 possible cases here:
+ //
+ // Cases for enabled == true
+ // Invariant: mVrState != PERSISTENT_VR_MODE;
+ // This is guaranteed as only this function sets mVrState to PERSISTENT_VR_MODE
+ // Invariant: mPersistentVrThreadTid == 0
+ // This is guaranteed by VrManagerService, which only emits callbacks when the
+ // mode changes, and in setPersistentVrThread, which only sets
+ // mPersistentVrThreadTid when mVrState = PERSISTENT_VR_MODE
+ // Case 1: mTopAppVrThreadTid > 0 (someone called setVrThread successfully and is
+ // the top-app)
+ // We reset the app which currently has SCHED_FIFO (mPersistentVrThreadTid) to
+ // SCHED_OTHER
+ // Case 2: mTopAppVrThreadTid == 0
+ // Do nothing
+ //
+ // Cases for enabled == false
+ // Invariant: mVrState == PERSISTENT_VR_MODE;
+ // This is guaranteed by VrManagerService, which only emits callbacks when the
+ // mode changes, and the only other assignment of mVrState outside of this
+ // function checks if mVrState != PERSISTENT_VR_MODE
+ // Invariant: mTopAppVrThreadTid == 0
+ // This is guaranteed in that mTopAppVrThreadTid is only set to a tid when
+ // mVrState is VR_MODE, and is explicitly set to 0 when setPersistentVrThread is
+ // called
+ // mPersistentVrThreadTid > 0 (someone called setPersistentVrThread successfully)
+ // 3. Reset mPersistentVrThreadTidto SCHED_OTHER
+ // mPersistentVrThreadTid == 0
+ // 4. Do nothing
+ if (enabled) {
+ mVrState = PERSISTENT_VR_MODE;
+ } else {
+ // Leaving persistent mode implies leaving VR mode.
+ mVrState = NON_VR_MODE;
+ }
+
+ if (mVrState == PERSISTENT_VR_MODE) {
+ if (mTopAppVrThreadTid > 0) {
+ // Ensure that when entering persistent VR mode the last top-app loses
+ // SCHED_FIFO.
+ Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
+ }
+ } else if (mPersistentVrThreadTid > 0) {
+ // Ensure that when leaving persistent VR mode we reschedule the high priority
+ // persistent thread.
+ Process.setThreadScheduler(mPersistentVrThreadTid, Process.SCHED_OTHER, 0);
+ mPersistentVrThreadTid = 0;
+ }
+ }
+ }
+ };
// Whether we should use SCHED_FIFO for UI and RenderThreads.
private boolean mUseFifoUiScheduling = false;
@@ -2325,28 +2389,36 @@
}
final ActivityRecord r = (ActivityRecord) msg.obj;
boolean vrMode;
+ boolean inVrMode;
ComponentName requestedPackage;
ComponentName callingPackage;
int userId;
synchronized (ActivityManagerService.this) {
vrMode = r.requestedVrComponent != null;
+ inVrMode = mVrState != NON_VR_MODE;
requestedPackage = r.requestedVrComponent;
userId = r.userId;
callingPackage = r.info.getComponentName();
- if (mInVrMode != vrMode) {
- mInVrMode = vrMode;
- mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), mInVrMode);
+ if (vrMode != inVrMode) {
+ // Don't change state if we're in persistent VR mode, but do update thread
+ // priorities if necessary.
+ if (mVrState != PERSISTENT_VR_MODE) {
+ mVrState = vrMode ? VR_MODE : NON_VR_MODE;
+ }
+ mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), vrMode);
if (r.app != null) {
ProcessRecord proc = r.app;
if (proc.vrThreadTid > 0) {
if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
try {
- if (mInVrMode == true) {
+ if (mVrState == VR_MODE) {
Process.setThreadScheduler(proc.vrThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ mTopAppVrThreadTid = proc.vrThreadTid;
} else {
Process.setThreadScheduler(proc.vrThreadTid,
Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
}
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed to set scheduling policy, thread does"
@@ -13010,54 +13082,101 @@
}
}
+ @Override
public void setVrThread(int tid) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
throw new UnsupportedOperationException("VR mode not supported on this device!");
}
synchronized (this) {
+ if (tid > 0 && mVrState == PERSISTENT_VR_MODE) {
+ Slog.e(TAG, "VR thread cannot be set in persistent VR mode!");
+ return;
+ }
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
final int pid = Binder.getCallingPid();
proc = mPidsSelfLocked.get(pid);
-
- if (proc != null && mInVrMode && tid >= 0) {
- // ensure the tid belongs to the process
- if (!Process.isThreadInProcess(pid, tid)) {
- throw new IllegalArgumentException("VR thread does not belong to process");
- }
-
- // reset existing VR thread to CFS if this thread still exists and belongs to
- // the calling process
- if (proc.vrThreadTid != 0
- && Process.isThreadInProcess(pid, proc.vrThreadTid)) {
- try {
- Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
- } catch (IllegalArgumentException e) {
- // Ignore this. Only occurs in race condition where previous VR thread
- // was destroyed during this method call.
- }
- }
-
- proc.vrThreadTid = tid;
-
- // promote to FIFO now if the tid is non-zero
- try {
- if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
- proc.vrThreadTid > 0) {
- Process.setThreadScheduler(proc.vrThreadTid,
- Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
- }
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Failed to set scheduling policy, thread does"
- + " not exist:\n" + e);
- }
+ if (proc != null && mVrState == VR_MODE && tid >= 0) {
+ proc.vrThreadTid = updateVrThreadLocked(proc, proc.vrThreadTid, pid, tid);
+ mTopAppVrThreadTid = proc.vrThreadTid;
}
}
}
}
@Override
+ public void setPersistentVrThread(int tid) {
+ if (checkCallingPermission(permission.RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) {
+ String msg = "Permission Denial: setPersistentVrThread() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + permission.RESTRICTED_VR_ACCESS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
+ throw new UnsupportedOperationException("VR mode not supported on this device!");
+ }
+
+ synchronized (this) {
+ // Disable any existing VR thread.
+ if (mTopAppVrThreadTid > 0) {
+ Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
+ }
+
+ if (tid > 0 && mVrState != PERSISTENT_VR_MODE) {
+ Slog.e(TAG, "Persistent VR thread may only be set in persistent VR mode!");
+ return;
+ }
+ ProcessRecord proc;
+ synchronized (mPidsSelfLocked) {
+ final int pid = Binder.getCallingPid();
+ mPersistentVrThreadTid =
+ updateVrThreadLocked(null, mPersistentVrThreadTid, pid, tid);
+ }
+ }
+ }
+
+ /**
+ * Used by setVrThread and setPersistentVrThread to update a thread's priority. When proc is
+ * non-null it must be in SCHED_GROUP_TOP_APP. When it is null, the tid is unconditionally
+ * rescheduled.
+ */
+ private int updateVrThreadLocked(ProcessRecord proc, int lastTid, int pid, int tid) {
+ // ensure the tid belongs to the process
+ if (!Process.isThreadInProcess(pid, tid)) {
+ throw new IllegalArgumentException("VR thread does not belong to process");
+ }
+
+ // reset existing VR thread to CFS if this thread still exists and belongs to
+ // the calling process
+ if (lastTid != 0 && Process.isThreadInProcess(pid, lastTid)) {
+ try {
+ Process.setThreadScheduler(lastTid, Process.SCHED_OTHER, 0);
+ } catch (IllegalArgumentException e) {
+ // Ignore this. Only occurs in race condition where previous VR thread
+ // was destroyed during this method call.
+ }
+ }
+
+ // promote to FIFO now if the tid is non-zero
+ try {
+ if ((proc == null || proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP)
+ && tid > 0) {
+ Process.setThreadScheduler(tid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ }
+ return tid;
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Failed to set scheduling policy, thread does"
+ + " not exist:\n" + e);
+ }
+ return lastTid;
+ }
+
+ @Override
public void setRenderThread(int tid) {
synchronized (this) {
ProcessRecord proc;
@@ -13643,7 +13762,10 @@
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
mAssistUtils = new AssistUtils(mContext);
-
+ VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class);
+ if (vrManagerInternal != null) {
+ vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener);
+ }
// Make sure we have the current profile info, since it is needed for security checks.
mUserController.onSystemReady();
mRecentTasks.onSystemReadyLocked();
@@ -19740,7 +19862,7 @@
mUserController.getCurrentUserIdLocked());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
- mShowDialogs = shouldShowDialogs(mTempConfig, mInVrMode);
+ mShowDialogs = shouldShowDialogs(mTempConfig, mVrState != NON_VR_MODE);
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
@@ -21279,10 +21401,11 @@
// do nothing if we already switched to RT
if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
// Switch VR thread for app to SCHED_FIFO
- if (mInVrMode && app.vrThreadTid != 0) {
+ if (mVrState == VR_MODE && app.vrThreadTid != 0) {
try {
Process.setThreadScheduler(app.vrThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ mTopAppVrThreadTid = app.vrThreadTid;
} catch (IllegalArgumentException e) {
// thread died, ignore
}
@@ -21330,6 +21453,7 @@
// Safe to do even if we're not in VR mode
if (app.vrThreadTid != 0) {
Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
}
if (mUseFifoUiScheduling) {
// Reset UI pipeline to SCHED_OTHER