/*
|
* 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 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 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 writeToProto(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);
|
}
|
}
|