content: /*
   * 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);
      }
  }
  