| /* |
| * 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 com.android.server.wm; |
| |
| import android.content.ComponentName; |
| import android.os.Process; |
| import android.service.vr.IPersistentVrStateCallbacks; |
| import android.util.Slog; |
| import android.util.proto.ProtoOutputStream; |
| import android.util.proto.ProtoUtils; |
| |
| import com.android.server.LocalServices; |
| import com.android.server.am.ActivityManagerService; |
| import com.android.server.am.ProcessList; |
| import com.android.server.am.VrControllerProto; |
| import com.android.server.vr.VrManagerInternal; |
| |
| /** |
| * Helper class for {@link ActivityManagerService} responsible for VrMode-related ActivityManager |
| * functionality. |
| * |
| * <p>Specifically, this class is responsible for: |
| * <ul> |
| * <li>Adjusting the scheduling of VR render threads while in VR mode. |
| * <li>Handling ActivityManager calls to set a VR or a 'persistent' VR thread. |
| * <li>Tracking the state of ActivityManagerService's view of VR-related behavior flags. |
| * </ul> |
| * |
| * <p>This is NOT the class that manages the system VR mode lifecycle. The class responsible for |
| * handling everything related to VR mode state changes (e.g. the lifecycles of the associated |
| * VrListenerService, VrStateCallbacks, VR HAL etc.) is VrManagerService. |
| * |
| * <p>This class is exclusively for use by ActivityManagerService. Do not add callbacks or other |
| * functionality to this for things that belong in VrManagerService. |
| */ |
| final class VrController { |
| private static final String TAG = "VrController"; |
| |
| // VR state flags. |
| private static final int FLAG_NON_VR_MODE = 0; |
| private static final int FLAG_VR_MODE = 1; |
| private static final int FLAG_PERSISTENT_VR_MODE = 2; |
| |
| // Keep the enum lists in sync |
| private static int[] ORIG_ENUMS = new int[] { |
| FLAG_NON_VR_MODE, |
| FLAG_VR_MODE, |
| FLAG_PERSISTENT_VR_MODE, |
| }; |
| private static int[] PROTO_ENUMS = new int[] { |
| VrControllerProto.FLAG_NON_VR_MODE, |
| VrControllerProto.FLAG_VR_MODE, |
| VrControllerProto.FLAG_PERSISTENT_VR_MODE, |
| }; |
| |
| // Invariants maintained for mVrState |
| // |
| // Always true: |
| // - Only a single VR-related thread will have elevated scheduling priorities at a time |
| // across all threads in all processes (and for all possible running modes). |
| // |
| // Always true while FLAG_PERSISTENT_VR_MODE is set: |
| // - An application has set a flag to run in persistent VR mode the next time VR mode is |
| // entered. The device may or may not be in VR mode. |
| // - mVrState will contain FLAG_PERSISTENT_VR_MODE |
| // - An application may set a persistent VR thread that gains elevated scheduling |
| // priorities via a call to setPersistentVrThread. |
| // - Calls to set a regular (non-persistent) VR thread via setVrThread will fail, and |
| // thread that had previously elevated its scheduling priority in this way is returned |
| // to its normal scheduling priority. |
| // |
| // Always true while FLAG_VR_MODE is set: |
| // - The current top application is running in VR mode. |
| // - mVrState will contain FLAG_VR_MODE |
| // |
| // While FLAG_VR_MODE is set without FLAG_PERSISTENT_VR_MODE: |
| // - The current top application may set one of its threads to run at an elevated |
| // scheduling priority via a call to setVrThread. |
| // |
| // While FLAG_VR_MODE is set with FLAG_PERSISTENT_VR_MODE: |
| // - The current top application may NOT set one of its threads to run at an elevated |
| // scheduling priority via a call to setVrThread (instead, the persistent VR thread will |
| // be kept if an application has set one). |
| // |
| // While mVrState == FLAG_NON_VR_MODE: |
| // - Calls to setVrThread will fail. |
| // - Calls to setPersistentVrThread will fail. |
| // - No threads will have elevated scheduling priority for VR. |
| // |
| private volatile int mVrState = FLAG_NON_VR_MODE; |
| |
| // The single VR render thread on the device that is given elevated scheduling priority. |
| private int mVrRenderThreadTid = 0; |
| |
| private final Object mGlobalAmLock; |
| |
| private final IPersistentVrStateCallbacks mPersistentVrModeListener = |
| new IPersistentVrStateCallbacks.Stub() { |
| @Override |
| public void onPersistentVrStateChanged(boolean enabled) { |
| synchronized(mGlobalAmLock) { |
| // Note: This is the only place where mVrState should have its |
| // FLAG_PERSISTENT_VR_MODE setting changed. |
| if (enabled) { |
| setVrRenderThreadLocked(0, ProcessList.SCHED_GROUP_TOP_APP, true); |
| mVrState |= FLAG_PERSISTENT_VR_MODE; |
| } else { |
| setPersistentVrRenderThreadLocked(0, true); |
| mVrState &= ~FLAG_PERSISTENT_VR_MODE; |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Create new VrController instance. |
| * |
| * @param globalAmLock the global ActivityManagerService lock. |
| */ |
| public VrController(final Object globalAmLock) { |
| mGlobalAmLock = globalAmLock; |
| } |
| |
| /** |
| * Called when ActivityManagerService receives its systemReady call during boot. |
| */ |
| public void onSystemReady() { |
| VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class); |
| if (vrManagerInternal != null) { |
| vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); |
| } |
| } |
| |
| /** |
| * Called without lock to determine whether to call {@link #onTopProcChangedLocked} in lock. It |
| * is used to optimize performance for the path that may have lock contention frequently. |
| */ |
| boolean isInterestingToSchedGroup() { |
| return (mVrState & (FLAG_VR_MODE | FLAG_PERSISTENT_VR_MODE)) != 0; |
| } |
| |
| /** |
| * Called when ActivityManagerService's TOP_APP process has changed. |
| * |
| * <p>Note: This must be called with the global ActivityManagerService lock held. |
| * |
| * @param proc is the WindowProcessController of the process that entered or left the TOP_APP |
| * scheduling group. |
| */ |
| public void onTopProcChangedLocked(WindowProcessController proc) { |
| final int curSchedGroup = proc.getCurrentSchedulingGroup(); |
| if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) { |
| setVrRenderThreadLocked(proc.mVrThreadTid, curSchedGroup, true); |
| } else { |
| if (proc.mVrThreadTid == mVrRenderThreadTid) { |
| clearVrRenderThreadLocked(true); |
| } |
| } |
| } |
| |
| /** |
| * Called when ActivityManagerService is switching VR mode for the TOP_APP process. |
| * |
| * @param record the ActivityRecord of the activity changing the system VR mode. |
| * @return {@code true} if the VR state changed. |
| */ |
| public boolean onVrModeChanged(ActivityRecord record) { |
| // This message means that the top focused activity enabled VR mode (or an activity |
| // that previously set this has become focused). |
| VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); |
| if (vrService == null) { |
| // VR mode isn't supported on this device. |
| return false; |
| } |
| boolean vrMode; |
| ComponentName requestedPackage; |
| ComponentName callingPackage; |
| int userId; |
| int processId = -1; |
| boolean changed = false; |
| synchronized (mGlobalAmLock) { |
| vrMode = record.requestedVrComponent != null; |
| requestedPackage = record.requestedVrComponent; |
| userId = record.mUserId; |
| callingPackage = record.info.getComponentName(); |
| |
| // Tell the VrController that a VR mode change is requested. |
| changed = changeVrModeLocked(vrMode, record.app); |
| |
| if (record.app != null) { |
| processId = record.app.getPid(); |
| } |
| } |
| |
| // Tell VrManager that a VR mode changed is requested, VrManager will handle |
| // notifying all non-AM dependencies if needed. |
| vrService.setVrMode(vrMode, requestedPackage, userId, processId, callingPackage); |
| return changed; |
| } |
| |
| /** |
| * Called to set an application's VR thread. |
| * |
| * <p>This will fail if the system is not in VR mode, the system has the persistent VR flag set, |
| * or the scheduling group of the thread is not for the current top app. If this succeeds, any |
| * previous VR thread will be returned to a normal sheduling priority; if this fails, the |
| * scheduling for the previous thread will be unaffected. |
| * |
| * <p>Note: This must be called with the global ActivityManagerService lock and the |
| * mPidsSelfLocked object locks held. |
| * |
| * @param tid the tid of the thread to set, or 0 to unset the current thread. |
| * @param pid the pid of the process owning the thread to set. |
| * @param proc the WindowProcessController of the process owning the thread to set. |
| */ |
| public void setVrThreadLocked(int tid, int pid, WindowProcessController proc) { |
| if (hasPersistentVrFlagSet()) { |
| Slog.w(TAG, "VR thread cannot be set in persistent VR mode!"); |
| return; |
| } |
| if (proc == null) { |
| Slog.w(TAG, "Persistent VR thread not set, calling process doesn't exist!"); |
| return; |
| } |
| if (tid != 0) { |
| enforceThreadInProcess(tid, pid); |
| } |
| if (!inVrMode()) { |
| Slog.w(TAG, "VR thread cannot be set when not in VR mode!"); |
| } else { |
| setVrRenderThreadLocked(tid, proc.getCurrentSchedulingGroup(), false); |
| } |
| proc.mVrThreadTid = (tid > 0) ? tid : 0; |
| } |
| |
| /** |
| * Called to set an application's persistent VR thread. |
| * |
| * <p>This will fail if the system does not have the persistent VR flag set. If this succeeds, |
| * any previous VR thread will be returned to a normal sheduling priority; if this fails, |
| * the scheduling for the previous thread will be unaffected. |
| * |
| * <p>Note: This must be called with the global ActivityManagerService lock and the |
| * mPidsSelfLocked object locks held. |
| * |
| * @param tid the tid of the thread to set, or 0 to unset the current thread. |
| * @param pid the pid of the process owning the thread to set. |
| * @param proc the process owning the thread to set. |
| */ |
| public void setPersistentVrThreadLocked(int tid, int pid, WindowProcessController proc) { |
| if (!hasPersistentVrFlagSet()) { |
| Slog.w(TAG, "Persistent VR thread may only be set in persistent VR mode!"); |
| return; |
| } |
| if (proc == null) { |
| Slog.w(TAG, "Persistent VR thread not set, calling process doesn't exist!"); |
| return; |
| } |
| if (tid != 0) { |
| enforceThreadInProcess(tid, pid); |
| } |
| setPersistentVrRenderThreadLocked(tid, false); |
| } |
| |
| /** |
| * Return {@code true} when UI features incompatible with VR mode should be disabled. |
| * |
| * <p>Note: This must be called with the global ActivityManagerService lock held. |
| */ |
| public boolean shouldDisableNonVrUiLocked() { |
| return mVrState != FLAG_NON_VR_MODE; |
| } |
| |
| /** |
| * Called when to update this VrController instance's state when the system VR mode is being |
| * changed. |
| * |
| * <p>Note: This must be called with the global ActivityManagerService lock held. |
| * |
| * @param vrMode {@code true} if the system VR mode is being enabled. |
| * @param proc the WindowProcessController of the process enabling the system VR mode. |
| * |
| * @return {@code true} if our state changed. |
| */ |
| private boolean changeVrModeLocked(boolean vrMode, WindowProcessController proc) { |
| final int oldVrState = mVrState; |
| |
| // This is the only place where mVrState should have its FLAG_VR_MODE setting |
| // changed. |
| if (vrMode) { |
| mVrState |= FLAG_VR_MODE; |
| } else { |
| mVrState &= ~FLAG_VR_MODE; |
| } |
| |
| boolean changed = (oldVrState != mVrState); |
| |
| if (changed) { |
| if (proc != null) { |
| if (proc.mVrThreadTid > 0) { |
| setVrRenderThreadLocked( |
| proc.mVrThreadTid, proc.getCurrentSchedulingGroup(), false); |
| } |
| } else { |
| clearVrRenderThreadLocked(false); |
| } |
| } |
| return changed; |
| } |
| |
| /** |
| * Set the given thread as the new VR thread, and give it special scheduling priority. |
| * |
| * <p>If the current thread is this thread, do nothing. If the current thread is different from |
| * the given thread, the current thread will be returned to a normal scheduling priority. |
| * |
| * @param newTid the tid of the thread to set, or 0 to unset the current thread. |
| * @param suppressLogs {@code true} if any error logging should be disabled. |
| * |
| * @return the tid of the thread configured to run at the scheduling priority for VR |
| * mode after this call completes (this may be the previous thread). |
| */ |
| private int updateVrRenderThreadLocked(int newTid, boolean suppressLogs) { |
| if (mVrRenderThreadTid == newTid) { |
| return mVrRenderThreadTid; |
| } |
| |
| if (mVrRenderThreadTid > 0) { |
| ActivityManagerService.scheduleAsRegularPriority(mVrRenderThreadTid, suppressLogs); |
| mVrRenderThreadTid = 0; |
| } |
| |
| if (newTid > 0) { |
| mVrRenderThreadTid = newTid; |
| ActivityManagerService.scheduleAsFifoPriority(mVrRenderThreadTid, suppressLogs); |
| } |
| return mVrRenderThreadTid; |
| } |
| |
| /** |
| * Set special scheduling for the given application persistent VR thread, if allowed. |
| * |
| * <p>This will fail if the system does not have the persistent VR flag set. If this succeeds, |
| * any previous VR thread will be returned to a normal sheduling priority; if this fails, |
| * the scheduling for the previous thread will be unaffected. |
| * |
| * @param newTid the tid of the thread to set, or 0 to unset the current thread. |
| * @param suppressLogs {@code true} if any error logging should be disabled. |
| * |
| * @return the tid of the thread configured to run at the scheduling priority for VR |
| * mode after this call completes (this may be the previous thread). |
| */ |
| private int setPersistentVrRenderThreadLocked(int newTid, boolean suppressLogs) { |
| if (!hasPersistentVrFlagSet()) { |
| if (!suppressLogs) { |
| Slog.w(TAG, "Failed to set persistent VR thread, " |
| + "system not in persistent VR mode."); |
| } |
| return mVrRenderThreadTid; |
| } |
| return updateVrRenderThreadLocked(newTid, suppressLogs); |
| } |
| |
| /** |
| * Set special scheduling for the given application VR thread, if allowed. |
| * |
| * <p>This will fail if the system is not in VR mode, the system has the persistent VR flag set, |
| * or the scheduling group of the thread is not for the current top app. If this succeeds, any |
| * previous VR thread will be returned to a normal sheduling priority; if this fails, the |
| * scheduling for the previous thread will be unaffected. |
| * |
| * @param newTid the tid of the thread to set, or 0 to unset the current thread. |
| * @param schedGroup the current scheduling group of the thread to set. |
| * @param suppressLogs {@code true} if any error logging should be disabled. |
| * |
| * @return the tid of the thread configured to run at the scheduling priority for VR |
| * mode after this call completes (this may be the previous thread). |
| */ |
| private int setVrRenderThreadLocked(int newTid, int schedGroup, boolean suppressLogs) { |
| boolean inVr = inVrMode(); |
| boolean inPersistentVr = hasPersistentVrFlagSet(); |
| if (!inVr || inPersistentVr || schedGroup != ProcessList.SCHED_GROUP_TOP_APP) { |
| if (!suppressLogs) { |
| String reason = "caller is not the current top application."; |
| if (!inVr) { |
| reason = "system not in VR mode."; |
| } else if (inPersistentVr) { |
| reason = "system in persistent VR mode."; |
| } |
| Slog.w(TAG, "Failed to set VR thread, " + reason); |
| } |
| return mVrRenderThreadTid; |
| } |
| return updateVrRenderThreadLocked(newTid, suppressLogs); |
| } |
| |
| /** |
| * Unset any special scheduling used for the current VR render thread, and return it to normal |
| * scheduling priority. |
| * |
| * @param suppressLogs {@code true} if any error logging should be disabled. |
| */ |
| private void clearVrRenderThreadLocked(boolean suppressLogs) { |
| updateVrRenderThreadLocked(0, suppressLogs); |
| } |
| |
| /** |
| * Check that the given tid is running in the process for the given pid, and throw an exception |
| * if not. |
| */ |
| private void enforceThreadInProcess(int tid, int pid) { |
| if (!Process.isThreadInProcess(pid, tid)) { |
| throw new IllegalArgumentException("VR thread does not belong to process"); |
| } |
| } |
| |
| /** |
| * True when the system is in VR mode. |
| */ |
| private boolean inVrMode() { |
| return (mVrState & FLAG_VR_MODE) != 0; |
| } |
| |
| /** |
| * True when the persistent VR mode flag has been set. |
| * |
| * Note: Currently this does not necessarily mean that the system is in VR mode. |
| */ |
| private boolean hasPersistentVrFlagSet() { |
| return (mVrState & FLAG_PERSISTENT_VR_MODE) != 0; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("[VrState=0x%x,VrRenderThreadTid=%d]", mVrState, mVrRenderThreadTid); |
| } |
| |
| void dumpDebug(ProtoOutputStream proto, long fieldId) { |
| final long token = proto.start(fieldId); |
| ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, VrControllerProto.VR_MODE, |
| mVrState, ORIG_ENUMS, PROTO_ENUMS); |
| proto.write(VrControllerProto.RENDER_THREAD_ID, mVrRenderThreadTid); |
| proto.end(token); |
| } |
| } |