content: /*
   * Copyright (C) 2014 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.job.controllers;
  
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.os.UserHandle;
  import android.util.ArraySet;
  import android.util.Slog;
  import android.util.proto.ProtoOutputStream;
  
  import com.android.internal.util.IndentingPrintWriter;
  import com.android.server.job.JobSchedulerService;
  import com.android.server.job.StateControllerProto;
  import com.android.server.job.controllers.idle.CarIdlenessTracker;
  import com.android.server.job.controllers.idle.DeviceIdlenessTracker;
  import com.android.server.job.controllers.idle.IdlenessListener;
  import com.android.server.job.controllers.idle.IdlenessTracker;
  
  import java.util.function.Predicate;
  
  public final class IdleController extends StateController implements IdlenessListener {
      private static final String TAG = "JobScheduler.IdleController";
      // Policy: we decide that we're "idle" if the device has been unused /
      // screen off or dreaming or wireless charging dock idle for at least this long
      final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
      IdlenessTracker mIdleTracker;
  
      public IdleController(JobSchedulerService service) {
          super(service);
          initIdleStateTracking(mContext);
      }
  
      /**
       * StateController interface
       */
      @Override
      public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
          if (taskStatus.hasIdleConstraint()) {
              mTrackedTasks.add(taskStatus);
              taskStatus.setTrackingController(JobStatus.TRACKING_IDLE);
              taskStatus.setIdleConstraintSatisfied(mIdleTracker.isIdle());
          }
      }
  
      @Override
      public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
              boolean forUpdate) {
          if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) {
              mTrackedTasks.remove(taskStatus);
          }
      }
  
      /**
       * State-change notifications from the idleness tracker
       */
      @Override
      public void reportNewIdleState(boolean isIdle) {
          synchronized (mLock) {
              for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
                  mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle);
              }
          }
          mStateChangedListener.onControllerStateChanged();
      }
  
      /**
       * Idle state tracking, and messaging with the task manager when
       * significant state changes occur
       */
      private void initIdleStateTracking(Context ctx) {
          final boolean isCar = mContext.getPackageManager().hasSystemFeature(
                  PackageManager.FEATURE_AUTOMOTIVE);
          if (isCar) {
              mIdleTracker = new CarIdlenessTracker();
          } else {
              mIdleTracker = new DeviceIdlenessTracker();
          }
          mIdleTracker.startTracking(ctx, this);
      }
  
      @Override
      public void dumpControllerStateLocked(IndentingPrintWriter pw,
              Predicate<JobStatus> predicate) {
          pw.println("Currently idle: " + mIdleTracker.isIdle());
          pw.println("Idleness tracker:"); mIdleTracker.dump(pw);
          pw.println();
  
          for (int i = 0; i < mTrackedTasks.size(); i++) {
              final JobStatus js = mTrackedTasks.valueAt(i);
              if (!predicate.test(js)) {
                  continue;
              }
              pw.print("#");
              js.printUniqueId(pw);
              pw.print(" from ");
              UserHandle.formatUid(pw, js.getSourceUid());
              pw.println();
          }
      }
  
      @Override
      public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
              Predicate<JobStatus> predicate) {
          final long token = proto.start(fieldId);
          final long mToken = proto.start(StateControllerProto.IDLE);
  
          proto.write(StateControllerProto.IdleController.IS_IDLE, mIdleTracker.isIdle());
  
          for (int i = 0; i < mTrackedTasks.size(); i++) {
              final JobStatus js = mTrackedTasks.valueAt(i);
              if (!predicate.test(js)) {
                  continue;
              }
              final long jsToken = proto.start(StateControllerProto.IdleController.TRACKED_JOBS);
              js.writeToShortProto(proto, StateControllerProto.IdleController.TrackedJob.INFO);
              proto.write(StateControllerProto.IdleController.TrackedJob.SOURCE_UID,
                      js.getSourceUid());
              proto.end(jsToken);
          }
  
          proto.end(mToken);
          proto.end(token);
      }
  }
  