From dc4796c209c1b604f3dfe0615c04c0fcf69f55c5 Mon Sep 17 00:00:00 2001 From: Craig Donner Date: Wed, 8 Mar 2017 09:51:47 -0800 Subject: [PATCH] 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 --- core/java/android/app/ActivityManager.java | 29 +++ core/java/android/app/IActivityManager.aidl | 1 + core/tests/coretests/AndroidManifest.xml | 3 + .../android/os/SetPersistentVrThreadTest.java | 162 ++++++++++++++ .../server/am/ActivityManagerService.java | 204 ++++++++++++++---- 5 files changed, 359 insertions(+), 40 deletions(-) create mode 100644 core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 043e0ab35e3e8..8a0af9a540561 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4060,6 +4060,10 @@ public class ActivityManager { * 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() @@ -4073,6 +4077,31 @@ public class ActivityManager { } } + /** + * 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 77edaeacfd2a1..f5e0c38c9f2f3 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -605,6 +605,7 @@ interface IActivityManager { ActivityManager.TaskSnapshot getTaskSnapshot(int taskId); void scheduleApplicationInfoChanged(in List 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 7b8c22962bccd..5669189434e8d 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -115,6 +115,9 @@ + + + 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 0000000000000..920988be2eb36 --- /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 { + 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 b40e70948c1a9..1649c8977cb02 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.firewall.IntentFirewall; 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub } 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,53 +13082,100 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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) { @@ -13643,7 +13762,10 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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