Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc4796c2 authored by Craig Donner's avatar Craig Donner
Browse files

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
parent 95c471d7
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -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()}
+1 −0
Original line number Diff line number Diff line
@@ -605,6 +605,7 @@ interface IActivityManager {
    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.
+3 −0
Original line number Diff line number Diff line
@@ -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" />
+162 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+161 −37
Original line number Diff line number Diff line
@@ -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,18 +13082,69 @@ 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 && 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);
            }
        }
    }
                if (proc != null && mInVrMode && tid >= 0) {
    /**
     * 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");
@@ -13029,32 +13152,28 @@ public class ActivityManagerService extends IActivityManager.Stub
        // 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)) {
        if (lastTid != 0 && Process.isThreadInProcess(pid, lastTid)) {
            try {
                            Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
                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.
            }
        }
                    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,
            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
@@ -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