Move the job scheduler service code to its own jar file.
- Also remove the dependency from SystemServiceRegistry to JobScheduler
See apex/jobscheduler/README_js-mainline.md for the details.
Bug: 137763703
Test: build and boot
Test: atest CtsJobSchedulerTestCases
Change-Id: I2386c78b7a6085d6e543a63f22cb620c4cabd06a
diff --git a/services/core/java/com/android/server/job/GrantedUriPermissions.java b/services/core/java/com/android/server/job/GrantedUriPermissions.java
deleted file mode 100644
index 005b189..0000000
--- a/services/core/java/com/android/server/job/GrantedUriPermissions.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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.job;
-
-import android.app.IActivityManager;
-import android.app.UriGrantsManager;
-import android.content.ClipData;
-import android.content.ContentProvider;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import com.android.server.LocalServices;
-import com.android.server.uri.UriGrantsManagerInternal;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public final class GrantedUriPermissions {
- private final int mGrantFlags;
- private final int mSourceUserId;
- private final String mTag;
- private final IBinder mPermissionOwner;
- private final ArrayList<Uri> mUris = new ArrayList<>();
-
- private GrantedUriPermissions(IActivityManager am, int grantFlags, int uid, String tag)
- throws RemoteException {
- mGrantFlags = grantFlags;
- mSourceUserId = UserHandle.getUserId(uid);
- mTag = tag;
- mPermissionOwner = LocalServices
- .getService(UriGrantsManagerInternal.class).newUriPermissionOwner("job: " + tag);
- }
-
- public void revoke(IActivityManager am) {
- for (int i = mUris.size()-1; i >= 0; i--) {
- LocalServices.getService(UriGrantsManagerInternal.class).revokeUriPermissionFromOwner(
- mPermissionOwner, mUris.get(i), mGrantFlags, mSourceUserId);
- }
- mUris.clear();
- }
-
- public static boolean checkGrantFlags(int grantFlags) {
- return (grantFlags & (Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- |Intent.FLAG_GRANT_READ_URI_PERMISSION)) != 0;
- }
-
- public static GrantedUriPermissions createFromIntent(IActivityManager am, Intent intent,
- int sourceUid, String targetPackage, int targetUserId, String tag) {
- int grantFlags = intent.getFlags();
- if (!checkGrantFlags(grantFlags)) {
- return null;
- }
-
- GrantedUriPermissions perms = null;
-
- Uri data = intent.getData();
- if (data != null) {
- perms = grantUri(am, data, sourceUid, targetPackage, targetUserId, grantFlags, tag,
- perms);
- }
-
- ClipData clip = intent.getClipData();
- if (clip != null) {
- perms = grantClip(am, clip, sourceUid, targetPackage, targetUserId, grantFlags, tag,
- perms);
- }
-
- return perms;
- }
-
- public static GrantedUriPermissions createFromClip(IActivityManager am, ClipData clip,
- int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag) {
- if (!checkGrantFlags(grantFlags)) {
- return null;
- }
- GrantedUriPermissions perms = null;
- if (clip != null) {
- perms = grantClip(am, clip, sourceUid, targetPackage, targetUserId, grantFlags,
- tag, perms);
- }
- return perms;
- }
-
- private static GrantedUriPermissions grantClip(IActivityManager am, ClipData clip,
- int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
- GrantedUriPermissions curPerms) {
- final int N = clip.getItemCount();
- for (int i = 0; i < N; i++) {
- curPerms = grantItem(am, clip.getItemAt(i), sourceUid, targetPackage, targetUserId,
- grantFlags, tag, curPerms);
- }
- return curPerms;
- }
-
- private static GrantedUriPermissions grantUri(IActivityManager am, Uri uri,
- int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
- GrantedUriPermissions curPerms) {
- try {
- int sourceUserId = ContentProvider.getUserIdFromUri(uri,
- UserHandle.getUserId(sourceUid));
- uri = ContentProvider.getUriWithoutUserId(uri);
- if (curPerms == null) {
- curPerms = new GrantedUriPermissions(am, grantFlags, sourceUid, tag);
- }
- UriGrantsManager.getService().grantUriPermissionFromOwner(curPerms.mPermissionOwner,
- sourceUid, targetPackage, uri, grantFlags, sourceUserId, targetUserId);
- curPerms.mUris.add(uri);
- } catch (RemoteException e) {
- Slog.e("JobScheduler", "AM dead");
- }
- return curPerms;
- }
-
- private static GrantedUriPermissions grantItem(IActivityManager am, ClipData.Item item,
- int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
- GrantedUriPermissions curPerms) {
- if (item.getUri() != null) {
- curPerms = grantUri(am, item.getUri(), sourceUid, targetPackage, targetUserId,
- grantFlags, tag, curPerms);
- }
- Intent intent = item.getIntent();
- if (intent != null && intent.getData() != null) {
- curPerms = grantUri(am, intent.getData(), sourceUid, targetPackage, targetUserId,
- grantFlags, tag, curPerms);
- }
- return curPerms;
- }
-
- // Dumpsys infrastructure
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mGrantFlags=0x"); pw.print(Integer.toHexString(mGrantFlags));
- pw.print(" mSourceUserId="); pw.println(mSourceUserId);
- pw.print(prefix); pw.print("mTag="); pw.println(mTag);
- pw.print(prefix); pw.print("mPermissionOwner="); pw.println(mPermissionOwner);
- for (int i = 0; i < mUris.size(); i++) {
- pw.print(prefix); pw.print("#"); pw.print(i); pw.print(": ");
- pw.println(mUris.get(i));
- }
- }
-
- public void dump(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(GrantedUriPermissionsDumpProto.FLAGS, mGrantFlags);
- proto.write(GrantedUriPermissionsDumpProto.SOURCE_USER_ID, mSourceUserId);
- proto.write(GrantedUriPermissionsDumpProto.TAG, mTag);
- proto.write(GrantedUriPermissionsDumpProto.PERMISSION_OWNER, mPermissionOwner.toString());
- for (int i = 0; i < mUris.size(); i++) {
- Uri u = mUris.get(i);
- if (u != null) {
- proto.write(GrantedUriPermissionsDumpProto.URIS, u.toString());
- }
- }
-
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/JobCompletedListener.java b/services/core/java/com/android/server/job/JobCompletedListener.java
deleted file mode 100644
index 34ba753b3..0000000
--- a/services/core/java/com/android/server/job/JobCompletedListener.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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;
-
-import com.android.server.job.controllers.JobStatus;
-
-/**
- * Used for communication between {@link com.android.server.job.JobServiceContext} and the
- * {@link com.android.server.job.JobSchedulerService}.
- */
-public interface JobCompletedListener {
- /**
- * Callback for when a job is completed.
- * @param needsReschedule Whether the implementing class should reschedule this job.
- */
- void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule);
-}
diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java
deleted file mode 100644
index bec1947..0000000
--- a/services/core/java/com/android/server/job/JobConcurrencyManager.java
+++ /dev/null
@@ -1,722 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-
-import android.app.ActivityManager;
-import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.StatLogger;
-import com.android.server.job.JobSchedulerService.Constants;
-import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
-import com.android.server.job.controllers.JobStatus;
-import com.android.server.job.controllers.StateController;
-
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * This class decides, given the various configuration and the system status, how many more jobs
- * can start.
- */
-class JobConcurrencyManager {
- private static final String TAG = JobSchedulerService.TAG;
- private static final boolean DEBUG = JobSchedulerService.DEBUG;
-
- private final Object mLock;
- private final JobSchedulerService mService;
- private final JobSchedulerService.Constants mConstants;
- private final Context mContext;
- private final Handler mHandler;
-
- private PowerManager mPowerManager;
-
- private boolean mCurrentInteractiveState;
- private boolean mEffectiveInteractiveState;
-
- private long mLastScreenOnRealtime;
- private long mLastScreenOffRealtime;
-
- private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
-
- /**
- * This array essentially stores the state of mActiveServices array.
- * The ith index stores the job present on the ith JobServiceContext.
- * We manipulate this array until we arrive at what jobs should be running on
- * what JobServiceContext.
- */
- JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
-
- boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT];
-
- int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
-
- /** Max job counts according to the current system state. */
- private JobSchedulerService.MaxJobCounts mMaxJobCounts;
-
- private final JobCountTracker mJobCountTracker = new JobCountTracker();
-
- /** Current memory trim level. */
- private int mLastMemoryTrimLevel;
-
- /** Used to throttle heavy API calls. */
- private long mNextSystemStateRefreshTime;
- private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
-
- private final StatLogger mStatLogger = new StatLogger(new String[]{
- "assignJobsToContexts",
- "refreshSystemState",
- });
-
- interface Stats {
- int ASSIGN_JOBS_TO_CONTEXTS = 0;
- int REFRESH_SYSTEM_STATE = 1;
-
- int COUNT = REFRESH_SYSTEM_STATE + 1;
- }
-
- JobConcurrencyManager(JobSchedulerService service) {
- mService = service;
- mLock = mService.mLock;
- mConstants = service.mConstants;
- mContext = service.getContext();
-
- mHandler = BackgroundThread.getHandler();
- }
-
- public void onSystemReady() {
- mPowerManager = mContext.getSystemService(PowerManager.class);
-
- final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mReceiver, filter);
-
- onInteractiveStateChanged(mPowerManager.isInteractive());
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case Intent.ACTION_SCREEN_ON:
- onInteractiveStateChanged(true);
- break;
- case Intent.ACTION_SCREEN_OFF:
- onInteractiveStateChanged(false);
- break;
- }
- }
- };
-
- /**
- * Called when the screen turns on / off.
- */
- private void onInteractiveStateChanged(boolean interactive) {
- synchronized (mLock) {
- if (mCurrentInteractiveState == interactive) {
- return;
- }
- mCurrentInteractiveState = interactive;
- if (DEBUG) {
- Slog.d(TAG, "Interactive: " + interactive);
- }
-
- final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
- if (interactive) {
- mLastScreenOnRealtime = nowRealtime;
- mEffectiveInteractiveState = true;
-
- mHandler.removeCallbacks(mRampUpForScreenOff);
- } else {
- mLastScreenOffRealtime = nowRealtime;
-
- // Set mEffectiveInteractiveState to false after the delay, when we may increase
- // the concurrency.
- // We don't need a wakeup alarm here. When there's a pending job, there should
- // also be jobs running too, meaning the device should be awake.
-
- // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
- // we need the exact same instance for removeCallbacks().
- mHandler.postDelayed(mRampUpForScreenOff,
- mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue());
- }
- }
- }
-
- private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
-
- /**
- * Called in {@link Constants#SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS} after
- * the screen turns off, in order to increase concurrency.
- */
- private void rampUpForScreenOff() {
- synchronized (mLock) {
- // Make sure the screen has really been off for the configured duration.
- // (There could be a race.)
- if (!mEffectiveInteractiveState) {
- return;
- }
- if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
- return;
- }
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- if ((mLastScreenOffRealtime
- + mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue())
- > now) {
- return;
- }
-
- mEffectiveInteractiveState = false;
-
- if (DEBUG) {
- Slog.d(TAG, "Ramping up concurrency");
- }
-
- mService.maybeRunPendingJobsLocked();
- }
- }
-
- private boolean isFgJob(JobStatus job) {
- return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP;
- }
-
- @GuardedBy("mLock")
- private void refreshSystemStateLocked() {
- final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
-
- // Only refresh the information every so often.
- if (nowUptime < mNextSystemStateRefreshTime) {
- return;
- }
-
- final long start = mStatLogger.getTime();
- mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL;
-
- mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
- try {
- mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel();
- } catch (RemoteException e) {
- }
-
- mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
- }
-
- @GuardedBy("mLock")
- private void updateMaxCountsLocked() {
- refreshSystemStateLocked();
-
- final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
- ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
- : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
-
-
- switch (mLastMemoryTrimLevel) {
- case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
- mMaxJobCounts = jobCounts.moderate;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
- mMaxJobCounts = jobCounts.low;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
- mMaxJobCounts = jobCounts.critical;
- break;
- default:
- mMaxJobCounts = jobCounts.normal;
- break;
- }
- }
-
- /**
- * Takes jobs from pending queue and runs them on available contexts.
- * If no contexts are available, preempts lower priority jobs to
- * run higher priority ones.
- * Lock on mJobs before calling this function.
- */
- @GuardedBy("mLock")
- void assignJobsToContextsLocked() {
- final long start = mStatLogger.getTime();
-
- assignJobsToContextsInternalLocked();
-
- mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
- }
-
- @GuardedBy("mLock")
- private void assignJobsToContextsInternalLocked() {
- if (DEBUG) {
- Slog.d(TAG, printPendingQueueLocked());
- }
-
- final JobPackageTracker tracker = mService.mJobPackageTracker;
- final List<JobStatus> pendingJobs = mService.mPendingJobs;
- final List<JobServiceContext> activeServices = mService.mActiveServices;
- final List<StateController> controllers = mService.mControllers;
-
- updateMaxCountsLocked();
-
- // To avoid GC churn, we recycle the arrays.
- JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
- boolean[] slotChanged = mRecycledSlotChanged;
- int[] preferredUidForContext = mRecycledPreferredUidForContext;
-
-
- // Initialize the work variables and also count running jobs.
- mJobCountTracker.reset(
- mMaxJobCounts.getMaxTotal(),
- mMaxJobCounts.getMaxBg(),
- mMaxJobCounts.getMinBg());
-
- for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
- final JobServiceContext js = mService.mActiveServices.get(i);
- final JobStatus status = js.getRunningJobLocked();
-
- if ((contextIdToJobMap[i] = status) != null) {
- mJobCountTracker.incrementRunningJobCount(isFgJob(status));
- }
-
- slotChanged[i] = false;
- preferredUidForContext[i] = js.getPreferredUid();
- }
- if (DEBUG) {
- Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
- }
-
- // Next, update the job priorities, and also count the pending FG / BG jobs.
- for (int i = 0; i < pendingJobs.size(); i++) {
- final JobStatus pending = pendingJobs.get(i);
-
- // If job is already running, go to next job.
- int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap);
- if (jobRunningContext != -1) {
- continue;
- }
-
- final int priority = mService.evaluateJobPriorityLocked(pending);
- pending.lastEvaluatedPriority = priority;
-
- mJobCountTracker.incrementPendingJobCount(isFgJob(pending));
- }
-
- mJobCountTracker.onCountDone();
-
- for (int i = 0; i < pendingJobs.size(); i++) {
- final JobStatus nextPending = pendingJobs.get(i);
-
- // Unfortunately we need to repeat this relatively expensive check.
- int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
- if (jobRunningContext != -1) {
- continue;
- }
-
- final boolean isPendingFg = isFgJob(nextPending);
-
- // Find an available slot for nextPending. The context should be available OR
- // it should have lowest priority among all running jobs
- // (sharing the same Uid as nextPending)
- int minPriorityForPreemption = Integer.MAX_VALUE;
- int selectedContextId = -1;
- boolean startingJob = false;
- for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
- JobStatus job = contextIdToJobMap[j];
- int preferredUid = preferredUidForContext[j];
- if (job == null) {
- final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
- || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
-
- if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) {
- // This slot is free, and we haven't yet hit the limit on
- // concurrent jobs... we can just throw the job in to here.
- selectedContextId = j;
- startingJob = true;
- break;
- }
- // No job on this context, but nextPending can't run here because
- // the context has a preferred Uid or we have reached the limit on
- // concurrent jobs.
- continue;
- }
- if (job.getUid() != nextPending.getUid()) {
- continue;
- }
-
- final int jobPriority = mService.evaluateJobPriorityLocked(job);
- if (jobPriority >= nextPending.lastEvaluatedPriority) {
- continue;
- }
-
- // TODO lastEvaluatedPriority should be evaluateJobPriorityLocked. (double check it)
- if (minPriorityForPreemption > nextPending.lastEvaluatedPriority) {
- minPriorityForPreemption = nextPending.lastEvaluatedPriority;
- selectedContextId = j;
- // In this case, we're just going to preempt a low priority job, we're not
- // actually starting a job, so don't set startingJob.
- }
- }
- if (selectedContextId != -1) {
- contextIdToJobMap[selectedContextId] = nextPending;
- slotChanged[selectedContextId] = true;
- }
- if (startingJob) {
- // Increase the counters when we're going to start a job.
- mJobCountTracker.onStartingNewJob(isPendingFg);
- }
- }
- if (DEBUG) {
- Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
- }
-
- mJobCountTracker.logStatus();
-
- tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
- mJobCountTracker.getFgRunningJobCountToNote());
-
- for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
- boolean preservePreferredUid = false;
- if (slotChanged[i]) {
- JobStatus js = activeServices.get(i).getRunningJobLocked();
- if (js != null) {
- if (DEBUG) {
- Slog.d(TAG, "preempting job: "
- + activeServices.get(i).getRunningJobLocked());
- }
- // preferredUid will be set to uid of currently running job.
- activeServices.get(i).preemptExecutingJobLocked();
- preservePreferredUid = true;
- } else {
- final JobStatus pendingJob = contextIdToJobMap[i];
- if (DEBUG) {
- Slog.d(TAG, "About to run job on context "
- + i + ", job: " + pendingJob);
- }
- for (int ic=0; ic<controllers.size(); ic++) {
- controllers.get(ic).prepareForExecutionLocked(pendingJob);
- }
- if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
- Slog.d(TAG, "Error executing " + pendingJob);
- }
- if (pendingJobs.remove(pendingJob)) {
- tracker.noteNonpending(pendingJob);
- }
- }
- }
- if (!preservePreferredUid) {
- activeServices.get(i).clearPreferredUid();
- }
- }
- }
-
- private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
- for (int i=0; i<map.length; i++) {
- if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
- return i;
- }
- }
- return -1;
- }
-
- @GuardedBy("mLock")
- private String printPendingQueueLocked() {
- StringBuilder s = new StringBuilder("Pending queue: ");
- Iterator<JobStatus> it = mService.mPendingJobs.iterator();
- while (it.hasNext()) {
- JobStatus js = it.next();
- s.append("(")
- .append(js.getJob().getId())
- .append(", ")
- .append(js.getUid())
- .append(") ");
- }
- return s.toString();
- }
-
- private static String printContextIdToJobMap(JobStatus[] map, String initial) {
- StringBuilder s = new StringBuilder(initial + ": ");
- for (int i=0; i<map.length; i++) {
- s.append("(")
- .append(map[i] == null? -1: map[i].getJobId())
- .append(map[i] == null? -1: map[i].getUid())
- .append(")" );
- }
- return s.toString();
- }
-
-
- public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
- pw.println("Concurrency:");
-
- pw.increaseIndent();
- try {
- pw.print("Screen state: current ");
- pw.print(mCurrentInteractiveState ? "ON" : "OFF");
- pw.print(" effective ");
- pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
- pw.println();
-
- pw.print("Last screen ON : ");
- TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
- pw.println();
-
- pw.print("Last screen OFF: ");
- TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
- pw.println();
-
- pw.println();
-
- pw.println("Current max jobs:");
- pw.println(" ");
- pw.println(mJobCountTracker);
-
- pw.println();
-
- pw.print("mLastMemoryTrimLevel: ");
- pw.print(mLastMemoryTrimLevel);
- pw.println();
-
- mStatLogger.dump(pw);
- } finally {
- pw.decreaseIndent();
- }
- }
-
- public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
- final long token = proto.start(tag);
-
- proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE,
- mCurrentInteractiveState);
- proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE,
- mEffectiveInteractiveState);
-
- proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
- nowRealtime - mLastScreenOnRealtime);
- proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
- nowRealtime - mLastScreenOffRealtime);
-
- mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
-
- proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL,
- mLastMemoryTrimLevel);
-
- proto.end(token);
- }
-
- /**
- * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
- * pending, how many more job can start.
- *
- * Extracted for testing and logging.
- */
- @VisibleForTesting
- static class JobCountTracker {
- private int mConfigNumMaxTotalJobs;
- private int mConfigNumMaxBgJobs;
- private int mConfigNumMinBgJobs;
-
- private int mNumRunningFgJobs;
- private int mNumRunningBgJobs;
-
- private int mNumPendingFgJobs;
- private int mNumPendingBgJobs;
-
- private int mNumStartingFgJobs;
- private int mNumStartingBgJobs;
-
- private int mNumReservedForBg;
- private int mNumActualMaxFgJobs;
- private int mNumActualMaxBgJobs;
-
- void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
- mConfigNumMaxTotalJobs = numTotalMaxJobs;
- mConfigNumMaxBgJobs = numMaxBgJobs;
- mConfigNumMinBgJobs = numMinBgJobs;
-
- mNumRunningFgJobs = 0;
- mNumRunningBgJobs = 0;
-
- mNumPendingFgJobs = 0;
- mNumPendingBgJobs = 0;
-
- mNumStartingFgJobs = 0;
- mNumStartingBgJobs = 0;
-
- mNumReservedForBg = 0;
- mNumActualMaxFgJobs = 0;
- mNumActualMaxBgJobs = 0;
- }
-
- void incrementRunningJobCount(boolean isFg) {
- if (isFg) {
- mNumRunningFgJobs++;
- } else {
- mNumRunningBgJobs++;
- }
- }
-
- void incrementPendingJobCount(boolean isFg) {
- if (isFg) {
- mNumPendingFgJobs++;
- } else {
- mNumPendingBgJobs++;
- }
- }
-
- void onStartingNewJob(boolean isFg) {
- if (isFg) {
- mNumStartingFgJobs++;
- } else {
- mNumStartingBgJobs++;
- }
- }
-
- void onCountDone() {
- // Note some variables are used only here but are made class members in order to have
- // them on logcat / dumpsys.
-
- // How many slots should we allocate to BG jobs at least?
- // That's basically "getMinBg()", but if there are less jobs, decrease it.
- // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.)
- final int reservedForBg = Math.min(
- mConfigNumMinBgJobs,
- mNumRunningBgJobs + mNumPendingBgJobs);
-
- // However, if there are FG jobs already running, we have to adjust it.
- mNumReservedForBg = Math.min(reservedForBg,
- mConfigNumMaxTotalJobs - mNumRunningFgJobs);
-
- // Max FG is [total - [number needed for BG jobs]]
- // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
- final int maxFg =
- mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
-
- // The above maxFg is the theoretical max. If there are less FG jobs, the actual
- // max FG will be lower accordingly.
- mNumActualMaxFgJobs = Math.min(
- maxFg,
- mNumRunningFgJobs + mNumPendingFgJobs);
-
- // Max BG is [total - actual max FG], but cap at [config max BG].
- final int maxBg = Math.min(
- mConfigNumMaxBgJobs,
- mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
-
- // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
- // This isn't needed for the logic to work, but this will give consistent output
- // on logcat and dumpsys.
- mNumActualMaxBgJobs = Math.min(
- maxBg,
- mNumRunningBgJobs + mNumPendingBgJobs);
- }
-
- boolean canJobStart(boolean isFg) {
- if (isFg) {
- return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs;
- } else {
- return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs;
- }
- }
-
- public int getNumStartingFgJobs() {
- return mNumStartingFgJobs;
- }
-
- public int getNumStartingBgJobs() {
- return mNumStartingBgJobs;
- }
-
- int getTotalRunningJobCountToNote() {
- return mNumRunningFgJobs + mNumRunningBgJobs
- + mNumStartingFgJobs + mNumStartingBgJobs;
- }
-
- int getFgRunningJobCountToNote() {
- return mNumRunningFgJobs + mNumStartingFgJobs;
- }
-
- void logStatus() {
- if (DEBUG) {
- Slog.d(TAG, "assignJobsToContexts: " + this);
- }
- }
-
- public String toString() {
- final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs;
- final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
- return String.format(
- "Config={tot=%d bg min/max=%d/%d}"
- + " Running[FG/BG (total)]: %d / %d (%d)"
- + " Pending: %d / %d (%d)"
- + " Actual max: %d%s / %d%s (%d%s)"
- + " Res BG: %d"
- + " Starting: %d / %d (%d)"
- + " Total: %d%s / %d%s (%d%s)",
- mConfigNumMaxTotalJobs,
- mConfigNumMinBgJobs,
- mConfigNumMaxBgJobs,
-
- mNumRunningFgJobs, mNumRunningBgJobs,
- mNumRunningFgJobs + mNumRunningBgJobs,
-
- mNumPendingFgJobs, mNumPendingBgJobs,
- mNumPendingFgJobs + mNumPendingBgJobs,
-
- mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
- mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
-
- mNumActualMaxFgJobs + mNumActualMaxBgJobs,
- (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
- ? "" : "*",
-
- mNumReservedForBg,
-
- mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
-
- totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
- totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
- totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
- );
- }
-
- public void dumpProto(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
- proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
- proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
-
- proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
- proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
-
- proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
- proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
-
- proto.end(token);
- }
- }
-}
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
deleted file mode 100644
index e28e5bd..0000000
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ /dev/null
@@ -1,653 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.JobSchedulerService.sSystemClock;
-import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
-
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.ArrayMap;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.RingBufferIndices;
-import com.android.server.job.controllers.JobStatus;
-
-import java.io.PrintWriter;
-
-public final class JobPackageTracker {
- // We batch every 30 minutes.
- static final long BATCHING_TIME = 30*60*1000;
- // Number of historical data sets we keep.
- static final int NUM_HISTORY = 5;
-
- private static final int EVENT_BUFFER_SIZE = 100;
-
- public static final int EVENT_CMD_MASK = 0xff;
- public static final int EVENT_STOP_REASON_SHIFT = 8;
- public static final int EVENT_STOP_REASON_MASK = 0xff << EVENT_STOP_REASON_SHIFT;
- public static final int EVENT_NULL = 0;
- public static final int EVENT_START_JOB = 1;
- public static final int EVENT_STOP_JOB = 2;
- public static final int EVENT_START_PERIODIC_JOB = 3;
- public static final int EVENT_STOP_PERIODIC_JOB = 4;
-
- private final RingBufferIndices mEventIndices = new RingBufferIndices(EVENT_BUFFER_SIZE);
- private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
- private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
- private final int[] mEventUids = new int[EVENT_BUFFER_SIZE];
- private final String[] mEventTags = new String[EVENT_BUFFER_SIZE];
- private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE];
- private final String[] mEventReasons = new String[EVENT_BUFFER_SIZE];
-
- public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason,
- String debugReason) {
- int index = mEventIndices.add();
- mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
- mEventTimes[index] = sElapsedRealtimeClock.millis();
- mEventUids[index] = uid;
- mEventTags[index] = tag;
- mEventJobIds[index] = jobId;
- mEventReasons[index] = debugReason;
- }
-
- DataSet mCurDataSet = new DataSet();
- DataSet[] mLastDataSets = new DataSet[NUM_HISTORY];
-
- final static class PackageEntry {
- long pastActiveTime;
- long activeStartTime;
- int activeNesting;
- int activeCount;
- boolean hadActive;
- long pastActiveTopTime;
- long activeTopStartTime;
- int activeTopNesting;
- int activeTopCount;
- boolean hadActiveTop;
- long pastPendingTime;
- long pendingStartTime;
- int pendingNesting;
- int pendingCount;
- boolean hadPending;
- final SparseIntArray stopReasons = new SparseIntArray();
-
- public long getActiveTime(long now) {
- long time = pastActiveTime;
- if (activeNesting > 0) {
- time += now - activeStartTime;
- }
- return time;
- }
-
- public long getActiveTopTime(long now) {
- long time = pastActiveTopTime;
- if (activeTopNesting > 0) {
- time += now - activeTopStartTime;
- }
- return time;
- }
-
- public long getPendingTime(long now) {
- long time = pastPendingTime;
- if (pendingNesting > 0) {
- time += now - pendingStartTime;
- }
- return time;
- }
- }
-
- final static class DataSet {
- final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>();
- final long mStartUptimeTime;
- final long mStartElapsedTime;
- final long mStartClockTime;
- long mSummedTime;
- int mMaxTotalActive;
- int mMaxFgActive;
-
- public DataSet(DataSet otherTimes) {
- mStartUptimeTime = otherTimes.mStartUptimeTime;
- mStartElapsedTime = otherTimes.mStartElapsedTime;
- mStartClockTime = otherTimes.mStartClockTime;
- }
-
- public DataSet() {
- mStartUptimeTime = sUptimeMillisClock.millis();
- mStartElapsedTime = sElapsedRealtimeClock.millis();
- mStartClockTime = sSystemClock.millis();
- }
-
- private PackageEntry getOrCreateEntry(int uid, String pkg) {
- ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
- if (uidMap == null) {
- uidMap = new ArrayMap<>();
- mEntries.put(uid, uidMap);
- }
- PackageEntry entry = uidMap.get(pkg);
- if (entry == null) {
- entry = new PackageEntry();
- uidMap.put(pkg, entry);
- }
- return entry;
- }
-
- public PackageEntry getEntry(int uid, String pkg) {
- ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
- if (uidMap == null) {
- return null;
- }
- return uidMap.get(pkg);
- }
-
- long getTotalTime(long now) {
- if (mSummedTime > 0) {
- return mSummedTime;
- }
- return now - mStartUptimeTime;
- }
-
- void incPending(int uid, String pkg, long now) {
- PackageEntry pe = getOrCreateEntry(uid, pkg);
- if (pe.pendingNesting == 0) {
- pe.pendingStartTime = now;
- pe.pendingCount++;
- }
- pe.pendingNesting++;
- }
-
- void decPending(int uid, String pkg, long now) {
- PackageEntry pe = getOrCreateEntry(uid, pkg);
- if (pe.pendingNesting == 1) {
- pe.pastPendingTime += now - pe.pendingStartTime;
- }
- pe.pendingNesting--;
- }
-
- void incActive(int uid, String pkg, long now) {
- PackageEntry pe = getOrCreateEntry(uid, pkg);
- if (pe.activeNesting == 0) {
- pe.activeStartTime = now;
- pe.activeCount++;
- }
- pe.activeNesting++;
- }
-
- void decActive(int uid, String pkg, long now, int stopReason) {
- PackageEntry pe = getOrCreateEntry(uid, pkg);
- if (pe.activeNesting == 1) {
- pe.pastActiveTime += now - pe.activeStartTime;
- }
- pe.activeNesting--;
- int count = pe.stopReasons.get(stopReason, 0);
- pe.stopReasons.put(stopReason, count+1);
- }
-
- void incActiveTop(int uid, String pkg, long now) {
- PackageEntry pe = getOrCreateEntry(uid, pkg);
- if (pe.activeTopNesting == 0) {
- pe.activeTopStartTime = now;
- pe.activeTopCount++;
- }
- pe.activeTopNesting++;
- }
-
- void decActiveTop(int uid, String pkg, long now, int stopReason) {
- PackageEntry pe = getOrCreateEntry(uid, pkg);
- if (pe.activeTopNesting == 1) {
- pe.pastActiveTopTime += now - pe.activeTopStartTime;
- }
- pe.activeTopNesting--;
- int count = pe.stopReasons.get(stopReason, 0);
- pe.stopReasons.put(stopReason, count+1);
- }
-
- void finish(DataSet next, long now) {
- for (int i = mEntries.size() - 1; i >= 0; i--) {
- ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
- for (int j = uidMap.size() - 1; j >= 0; j--) {
- PackageEntry pe = uidMap.valueAt(j);
- if (pe.activeNesting > 0 || pe.activeTopNesting > 0 || pe.pendingNesting > 0) {
- // Propagate existing activity in to next data set.
- PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
- nextPe.activeStartTime = now;
- nextPe.activeNesting = pe.activeNesting;
- nextPe.activeTopStartTime = now;
- nextPe.activeTopNesting = pe.activeTopNesting;
- nextPe.pendingStartTime = now;
- nextPe.pendingNesting = pe.pendingNesting;
- // Finish it off.
- if (pe.activeNesting > 0) {
- pe.pastActiveTime += now - pe.activeStartTime;
- pe.activeNesting = 0;
- }
- if (pe.activeTopNesting > 0) {
- pe.pastActiveTopTime += now - pe.activeTopStartTime;
- pe.activeTopNesting = 0;
- }
- if (pe.pendingNesting > 0) {
- pe.pastPendingTime += now - pe.pendingStartTime;
- pe.pendingNesting = 0;
- }
- }
- }
- }
- }
-
- void addTo(DataSet out, long now) {
- out.mSummedTime += getTotalTime(now);
- for (int i = mEntries.size() - 1; i >= 0; i--) {
- ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
- for (int j = uidMap.size() - 1; j >= 0; j--) {
- PackageEntry pe = uidMap.valueAt(j);
- PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
- outPe.pastActiveTime += pe.pastActiveTime;
- outPe.activeCount += pe.activeCount;
- outPe.pastActiveTopTime += pe.pastActiveTopTime;
- outPe.activeTopCount += pe.activeTopCount;
- outPe.pastPendingTime += pe.pastPendingTime;
- outPe.pendingCount += pe.pendingCount;
- if (pe.activeNesting > 0) {
- outPe.pastActiveTime += now - pe.activeStartTime;
- outPe.hadActive = true;
- }
- if (pe.activeTopNesting > 0) {
- outPe.pastActiveTopTime += now - pe.activeTopStartTime;
- outPe.hadActiveTop = true;
- }
- if (pe.pendingNesting > 0) {
- outPe.pastPendingTime += now - pe.pendingStartTime;
- outPe.hadPending = true;
- }
- for (int k = pe.stopReasons.size()-1; k >= 0; k--) {
- int type = pe.stopReasons.keyAt(k);
- outPe.stopReasons.put(type, outPe.stopReasons.get(type, 0)
- + pe.stopReasons.valueAt(k));
- }
- }
- }
- if (mMaxTotalActive > out.mMaxTotalActive) {
- out.mMaxTotalActive = mMaxTotalActive;
- }
- if (mMaxFgActive > out.mMaxFgActive) {
- out.mMaxFgActive = mMaxFgActive;
- }
- }
-
- void printDuration(PrintWriter pw, long period, long duration, int count, String suffix) {
- float fraction = duration / (float) period;
- int percent = (int) ((fraction * 100) + .5f);
- if (percent > 0) {
- pw.print(" ");
- pw.print(percent);
- pw.print("% ");
- pw.print(count);
- pw.print("x ");
- pw.print(suffix);
- } else if (count > 0) {
- pw.print(" ");
- pw.print(count);
- pw.print("x ");
- pw.print(suffix);
- }
- }
-
- void dump(PrintWriter pw, String header, String prefix, long now, long nowElapsed,
- int filterUid) {
- final long period = getTotalTime(now);
- pw.print(prefix); pw.print(header); pw.print(" at ");
- pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString());
- pw.print(" (");
- TimeUtils.formatDuration(mStartElapsedTime, nowElapsed, pw);
- pw.print(") over ");
- TimeUtils.formatDuration(period, pw);
- pw.println(":");
- final int NE = mEntries.size();
- for (int i = 0; i < NE; i++) {
- int uid = mEntries.keyAt(i);
- if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
- continue;
- }
- ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
- final int NP = uidMap.size();
- for (int j = 0; j < NP; j++) {
- PackageEntry pe = uidMap.valueAt(j);
- pw.print(prefix); pw.print(" ");
- UserHandle.formatUid(pw, uid);
- pw.print(" / "); pw.print(uidMap.keyAt(j));
- pw.println(":");
- pw.print(prefix); pw.print(" ");
- printDuration(pw, period, pe.getPendingTime(now), pe.pendingCount, "pending");
- printDuration(pw, period, pe.getActiveTime(now), pe.activeCount, "active");
- printDuration(pw, period, pe.getActiveTopTime(now), pe.activeTopCount,
- "active-top");
- if (pe.pendingNesting > 0 || pe.hadPending) {
- pw.print(" (pending)");
- }
- if (pe.activeNesting > 0 || pe.hadActive) {
- pw.print(" (active)");
- }
- if (pe.activeTopNesting > 0 || pe.hadActiveTop) {
- pw.print(" (active-top)");
- }
- pw.println();
- if (pe.stopReasons.size() > 0) {
- pw.print(prefix); pw.print(" ");
- for (int k = 0; k < pe.stopReasons.size(); k++) {
- if (k > 0) {
- pw.print(", ");
- }
- pw.print(pe.stopReasons.valueAt(k));
- pw.print("x ");
- pw.print(JobParameters.getReasonName(pe.stopReasons.keyAt(k)));
- }
- pw.println();
- }
- }
- }
- pw.print(prefix); pw.print(" Max concurrency: ");
- pw.print(mMaxTotalActive); pw.print(" total, ");
- pw.print(mMaxFgActive); pw.println(" foreground");
- }
-
- private void printPackageEntryState(ProtoOutputStream proto, long fieldId,
- long duration, int count) {
- final long token = proto.start(fieldId);
- proto.write(DataSetProto.PackageEntryProto.State.DURATION_MS, duration);
- proto.write(DataSetProto.PackageEntryProto.State.COUNT, count);
- proto.end(token);
- }
-
- void dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid) {
- final long token = proto.start(fieldId);
- final long period = getTotalTime(now);
-
- proto.write(DataSetProto.START_CLOCK_TIME_MS, mStartClockTime);
- proto.write(DataSetProto.ELAPSED_TIME_MS, nowElapsed - mStartElapsedTime);
- proto.write(DataSetProto.PERIOD_MS, period);
-
- final int NE = mEntries.size();
- for (int i = 0; i < NE; i++) {
- int uid = mEntries.keyAt(i);
- if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
- continue;
- }
- ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
- final int NP = uidMap.size();
- for (int j = 0; j < NP; j++) {
- final long peToken = proto.start(DataSetProto.PACKAGE_ENTRIES);
- PackageEntry pe = uidMap.valueAt(j);
-
- proto.write(DataSetProto.PackageEntryProto.UID, uid);
- proto.write(DataSetProto.PackageEntryProto.PACKAGE_NAME, uidMap.keyAt(j));
-
- printPackageEntryState(proto, DataSetProto.PackageEntryProto.PENDING_STATE,
- pe.getPendingTime(now), pe.pendingCount);
- printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_STATE,
- pe.getActiveTime(now), pe.activeCount);
- printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_TOP_STATE,
- pe.getActiveTopTime(now), pe.activeTopCount);
-
- proto.write(DataSetProto.PackageEntryProto.PENDING,
- pe.pendingNesting > 0 || pe.hadPending);
- proto.write(DataSetProto.PackageEntryProto.ACTIVE,
- pe.activeNesting > 0 || pe.hadActive);
- proto.write(DataSetProto.PackageEntryProto.ACTIVE_TOP,
- pe.activeTopNesting > 0 || pe.hadActiveTop);
-
- for (int k = 0; k < pe.stopReasons.size(); k++) {
- final long srcToken =
- proto.start(DataSetProto.PackageEntryProto.STOP_REASONS);
-
- proto.write(DataSetProto.PackageEntryProto.StopReasonCount.REASON,
- pe.stopReasons.keyAt(k));
- proto.write(DataSetProto.PackageEntryProto.StopReasonCount.COUNT,
- pe.stopReasons.valueAt(k));
-
- proto.end(srcToken);
- }
-
- proto.end(peToken);
- }
- }
-
- proto.write(DataSetProto.MAX_CONCURRENCY, mMaxTotalActive);
- proto.write(DataSetProto.MAX_FOREGROUND_CONCURRENCY, mMaxFgActive);
-
- proto.end(token);
- }
- }
-
- void rebatchIfNeeded(long now) {
- long totalTime = mCurDataSet.getTotalTime(now);
- if (totalTime > BATCHING_TIME) {
- DataSet last = mCurDataSet;
- last.mSummedTime = totalTime;
- mCurDataSet = new DataSet();
- last.finish(mCurDataSet, now);
- System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1);
- mLastDataSets[0] = last;
- }
- }
-
- public void notePending(JobStatus job) {
- final long now = sUptimeMillisClock.millis();
- job.madePending = now;
- rebatchIfNeeded(now);
- mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
- }
-
- public void noteNonpending(JobStatus job) {
- final long now = sUptimeMillisClock.millis();
- mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
- rebatchIfNeeded(now);
- }
-
- public void noteActive(JobStatus job) {
- final long now = sUptimeMillisClock.millis();
- job.madeActive = now;
- rebatchIfNeeded(now);
- if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
- mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
- } else {
- mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
- }
- addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB : EVENT_START_JOB,
- job.getSourceUid(), job.getBatteryName(), job.getJobId(), 0, null);
- }
-
- public void noteInactive(JobStatus job, int stopReason, String debugReason) {
- final long now = sUptimeMillisClock.millis();
- if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
- mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
- stopReason);
- } else {
- mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason);
- }
- rebatchIfNeeded(now);
- addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB : EVENT_STOP_PERIODIC_JOB,
- job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason, debugReason);
- }
-
- public void noteConcurrency(int totalActive, int fgActive) {
- if (totalActive > mCurDataSet.mMaxTotalActive) {
- mCurDataSet.mMaxTotalActive = totalActive;
- }
- if (fgActive > mCurDataSet.mMaxFgActive) {
- mCurDataSet.mMaxFgActive = fgActive;
- }
- }
-
- public float getLoadFactor(JobStatus job) {
- final int uid = job.getSourceUid();
- final String pkg = job.getSourcePackageName();
- PackageEntry cur = mCurDataSet.getEntry(uid, pkg);
- PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null;
- if (cur == null && last == null) {
- return 0;
- }
- final long now = sUptimeMillisClock.millis();
- long time = 0;
- if (cur != null) {
- time += cur.getActiveTime(now) + cur.getPendingTime(now);
- }
- long period = mCurDataSet.getTotalTime(now);
- if (last != null) {
- time += last.getActiveTime(now) + last.getPendingTime(now);
- period += mLastDataSets[0].getTotalTime(now);
- }
- return time / (float)period;
- }
-
- public void dump(PrintWriter pw, String prefix, int filterUid) {
- final long now = sUptimeMillisClock.millis();
- final long nowElapsed = sElapsedRealtimeClock.millis();
- final DataSet total;
- if (mLastDataSets[0] != null) {
- total = new DataSet(mLastDataSets[0]);
- mLastDataSets[0].addTo(total, now);
- } else {
- total = new DataSet(mCurDataSet);
- }
- mCurDataSet.addTo(total, now);
- for (int i = 1; i < mLastDataSets.length; i++) {
- if (mLastDataSets[i] != null) {
- mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowElapsed, filterUid);
- pw.println();
- }
- }
- total.dump(pw, "Current stats", prefix, now, nowElapsed, filterUid);
- }
-
- public void dump(ProtoOutputStream proto, long fieldId, int filterUid) {
- final long token = proto.start(fieldId);
- final long now = sUptimeMillisClock.millis();
- final long nowElapsed = sElapsedRealtimeClock.millis();
-
- final DataSet total;
- if (mLastDataSets[0] != null) {
- total = new DataSet(mLastDataSets[0]);
- mLastDataSets[0].addTo(total, now);
- } else {
- total = new DataSet(mCurDataSet);
- }
- mCurDataSet.addTo(total, now);
-
- for (int i = 1; i < mLastDataSets.length; i++) {
- if (mLastDataSets[i] != null) {
- mLastDataSets[i].dump(proto, JobPackageTrackerDumpProto.HISTORICAL_STATS,
- now, nowElapsed, filterUid);
- }
- }
- total.dump(proto, JobPackageTrackerDumpProto.CURRENT_STATS,
- now, nowElapsed, filterUid);
-
- proto.end(token);
- }
-
- public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) {
- final int size = mEventIndices.size();
- if (size <= 0) {
- return false;
- }
- pw.println(" Job history:");
- final long now = sElapsedRealtimeClock.millis();
- for (int i=0; i<size; i++) {
- final int index = mEventIndices.indexOf(i);
- final int uid = mEventUids[index];
- if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
- continue;
- }
- final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
- if (cmd == EVENT_NULL) {
- continue;
- }
- final String label;
- switch (cmd) {
- case EVENT_START_JOB: label = " START"; break;
- case EVENT_STOP_JOB: label = " STOP"; break;
- case EVENT_START_PERIODIC_JOB: label = "START-P"; break;
- case EVENT_STOP_PERIODIC_JOB: label = " STOP-P"; break;
- default: label = " ??"; break;
- }
- pw.print(prefix);
- TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
- pw.print(" ");
- pw.print(label);
- pw.print(": #");
- UserHandle.formatUid(pw, uid);
- pw.print("/");
- pw.print(mEventJobIds[index]);
- pw.print(" ");
- pw.print(mEventTags[index]);
- if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
- pw.print(" ");
- final String reason = mEventReasons[index];
- if (reason != null) {
- pw.print(mEventReasons[index]);
- } else {
- pw.print(JobParameters.getReasonName((mEventCmds[index] & EVENT_STOP_REASON_MASK)
- >> EVENT_STOP_REASON_SHIFT));
- }
- }
- pw.println();
- }
- return true;
- }
-
- public void dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid) {
- final int size = mEventIndices.size();
- if (size == 0) {
- return;
- }
- final long token = proto.start(fieldId);
-
- final long now = sElapsedRealtimeClock.millis();
- for (int i = 0; i < size; i++) {
- final int index = mEventIndices.indexOf(i);
- final int uid = mEventUids[index];
- if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
- continue;
- }
- final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
- if (cmd == EVENT_NULL) {
- continue;
- }
- final long heToken = proto.start(JobPackageHistoryProto.HISTORY_EVENT);
-
- proto.write(JobPackageHistoryProto.HistoryEvent.EVENT, cmd);
- proto.write(JobPackageHistoryProto.HistoryEvent.TIME_SINCE_EVENT_MS, now - mEventTimes[index]);
- proto.write(JobPackageHistoryProto.HistoryEvent.UID, uid);
- proto.write(JobPackageHistoryProto.HistoryEvent.JOB_ID, mEventJobIds[index]);
- proto.write(JobPackageHistoryProto.HistoryEvent.TAG, mEventTags[index]);
- if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
- proto.write(JobPackageHistoryProto.HistoryEvent.STOP_REASON,
- (mEventCmds[index] & EVENT_STOP_REASON_MASK) >> EVENT_STOP_REASON_SHIFT);
- }
-
- proto.end(heToken);
- }
-
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/JobSchedulerInternal.java b/services/core/java/com/android/server/job/JobSchedulerInternal.java
deleted file mode 100644
index 425ec47..0000000
--- a/services/core/java/com/android/server/job/JobSchedulerInternal.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.annotation.UserIdInt;
-import android.app.job.JobInfo;
-
-import java.util.List;
-
-/**
- * JobScheduler local system service interface.
- * {@hide} Only for use within the system server.
- */
-public interface JobSchedulerInternal {
-
- // Bookkeeping about app standby bucket scheduling
-
- /**
- * The current bucket heartbeat ordinal
- */
- long currentHeartbeat();
-
- /**
- * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
- */
- long nextHeartbeatForBucket(int bucket);
-
- /**
- * Heartbeat ordinal for the given app. This is typically the heartbeat at which
- * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
- * jobs in a long time is immediately runnable even if the app is bucketed into
- * an infrequent time allocation.
- */
- public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket);
-
- /**
- * Tell the scheduler when a JobServiceContext starts running a job in an app
- */
- void noteJobStart(String packageName, int userId);
-
- /**
- * Returns a list of pending jobs scheduled by the system service.
- */
- List<JobInfo> getSystemScheduledPendingJobs();
-
- /**
- * Cancel the jobs for a given uid (e.g. when app data is cleared)
- */
- void cancelJobsForUid(int uid, String reason);
-
- /**
- * These are for activity manager to communicate to use what is currently performing backups.
- */
- void addBackingUpUid(int uid);
- void removeBackingUpUid(int uid);
- void clearAllBackingUpUids();
-
- /**
- * The user has started interacting with the app. Take any appropriate action.
- */
- void reportAppUsage(String packageName, int userId);
-
- /**
- * Report a snapshot of sync-related jobs back to the sync manager
- */
- JobStorePersistStats getPersistStats();
-
- /**
- * Stats about the first load after boot and the most recent save.
- */
- public class JobStorePersistStats {
- public int countAllJobsLoaded = -1;
- public int countSystemServerJobsLoaded = -1;
- public int countSystemSyncManagerJobsLoaded = -1;
-
- public int countAllJobsSaved = -1;
- public int countSystemServerJobsSaved = -1;
- public int countSystemSyncManagerJobsSaved = -1;
-
- public JobStorePersistStats() {
- }
-
- public JobStorePersistStats(JobStorePersistStats source) {
- countAllJobsLoaded = source.countAllJobsLoaded;
- countSystemServerJobsLoaded = source.countSystemServerJobsLoaded;
- countSystemSyncManagerJobsLoaded = source.countSystemSyncManagerJobsLoaded;
-
- countAllJobsSaved = source.countAllJobsSaved;
- countSystemServerJobsSaved = source.countSystemServerJobsSaved;
- countSystemSyncManagerJobsSaved = source.countSystemSyncManagerJobsSaved;
- }
-
- @Override
- public String toString() {
- return "FirstLoad: "
- + countAllJobsLoaded + "/"
- + countSystemServerJobsLoaded + "/"
- + countSystemSyncManagerJobsLoaded
- + " LastSave: "
- + countAllJobsSaved + "/"
- + countSystemServerJobsSaved + "/"
- + countSystemSyncManagerJobsSaved;
- }
- }
-}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
deleted file mode 100644
index e44e902..0000000
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ /dev/null
@@ -1,3657 +0,0 @@
-/*
- * 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;
-
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.AlarmManager;
-import android.app.AppGlobals;
-import android.app.IUidObserver;
-import android.app.job.IJobScheduler;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobProtoEnums;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.app.job.JobSnapshot;
-import android.app.job.JobWorkItem;
-import android.app.usage.UsageStatsManager;
-import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ServiceInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.BatteryStats;
-import android.os.BatteryStatsInternal;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.Temperature;
-import android.os.UserHandle;
-import android.os.UserManagerInternal;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.util.KeyValueListParser;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.StatsLog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-import com.android.server.AppStateTracker;
-import com.android.server.DeviceIdleController;
-import com.android.server.FgThread;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
-import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
-import com.android.server.job.JobSchedulerServiceDumpProto.RegisteredJob;
-import com.android.server.job.controllers.BackgroundJobsController;
-import com.android.server.job.controllers.BatteryController;
-import com.android.server.job.controllers.ConnectivityController;
-import com.android.server.job.controllers.ContentObserverController;
-import com.android.server.job.controllers.DeviceIdleJobsController;
-import com.android.server.job.controllers.IdleController;
-import com.android.server.job.controllers.JobStatus;
-import com.android.server.job.controllers.QuotaController;
-import com.android.server.job.controllers.StateController;
-import com.android.server.job.controllers.StorageController;
-import com.android.server.job.controllers.TimeController;
-
-import libcore.util.EmptyArray;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.time.Clock;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Responsible for taking jobs representing work to be performed by a client app, and determining
- * based on the criteria specified when that job should be run against the client application's
- * endpoint.
- * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
- * about constraints, or the state of active jobs. It receives callbacks from the various
- * controllers and completed jobs and operates accordingly.
- *
- * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
- * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
- * @hide
- */
-public class JobSchedulerService extends com.android.server.SystemService
- implements StateChangedListener, JobCompletedListener {
- public static final String TAG = "JobScheduler";
- public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- public static final boolean DEBUG_STANDBY = DEBUG || false;
-
- /** The maximum number of concurrent jobs we run at one time. */
- static final int MAX_JOB_CONTEXTS_COUNT = 16;
- /** Enforce a per-app limit on scheduled jobs? */
- private static final boolean ENFORCE_MAX_JOBS = true;
- /** The maximum number of jobs that we allow an unprivileged app to schedule */
- private static final int MAX_JOBS_PER_APP = 100;
-
- @VisibleForTesting
- public static Clock sSystemClock = Clock.systemUTC();
- @VisibleForTesting
- public static Clock sUptimeMillisClock = SystemClock.uptimeClock();
- @VisibleForTesting
- public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
-
- /** Global local for all job scheduler state. */
- final Object mLock = new Object();
- /** Master list of jobs. */
- final JobStore mJobs;
- /** Tracking the standby bucket state of each app */
- final StandbyTracker mStandbyTracker;
- /** Tracking amount of time each package runs for. */
- final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
- final JobConcurrencyManager mConcurrencyManager;
-
- static final int MSG_JOB_EXPIRED = 0;
- static final int MSG_CHECK_JOB = 1;
- static final int MSG_STOP_JOB = 2;
- static final int MSG_CHECK_JOB_GREEDY = 3;
- static final int MSG_UID_STATE_CHANGED = 4;
- static final int MSG_UID_GONE = 5;
- static final int MSG_UID_ACTIVE = 6;
- static final int MSG_UID_IDLE = 7;
-
- /**
- * Track Services that have currently active or pending jobs. The index is provided by
- * {@link JobStatus#getServiceToken()}
- */
- final List<JobServiceContext> mActiveServices = new ArrayList<>();
-
- /** List of controllers that will notify this service of updates to jobs. */
- final List<StateController> mControllers;
- /** Need direct access to this for testing. */
- private final BatteryController mBatteryController;
- /** Need direct access to this for testing. */
- private final StorageController mStorageController;
- /** Need directly for sending uid state changes */
- private final DeviceIdleJobsController mDeviceIdleJobsController;
- /** Needed to get remaining quota time. */
- private final QuotaController mQuotaController;
-
- /** Need directly for receiving thermal events */
- private IThermalService mThermalService;
- /** Thermal constraint. */
- @GuardedBy("mLock")
- private boolean mThermalConstraint = false;
-
- /**
- * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
- * when ready to execute them.
- */
- final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
-
- int[] mStartedUsers = EmptyArray.INT;
-
- final JobHandler mHandler;
- final JobSchedulerStub mJobSchedulerStub;
-
- PackageManagerInternal mLocalPM;
- ActivityManagerInternal mActivityManagerInternal;
- IBatteryStats mBatteryStats;
- DeviceIdleController.LocalService mLocalDeviceIdleController;
- AppStateTracker mAppStateTracker;
- final UsageStatsManagerInternal mUsageStats;
-
- /**
- * Set to true once we are allowed to run third party apps.
- */
- boolean mReadyToRock;
-
- /**
- * What we last reported to DeviceIdleController about whether we are active.
- */
- boolean mReportedActive;
-
- /**
- * Are we currently in device-wide standby parole?
- */
- volatile boolean mInParole;
-
- /**
- * A mapping of which uids are currently in the foreground to their effective priority.
- */
- final SparseIntArray mUidPriorityOverride = new SparseIntArray();
-
- /**
- * Which uids are currently performing backups, so we shouldn't allow their jobs to run.
- */
- final SparseIntArray mBackingUpUids = new SparseIntArray();
-
- /**
- * Count standby heartbeats, and keep track of which beat each bucket's jobs will
- * next become runnable. Index into this array is by normalized bucket:
- * { ACTIVE, WORKING, FREQUENT, RARE, NEVER }. The ACTIVE and NEVER bucket
- * milestones are not updated: ACTIVE apps get jobs whenever they ask for them,
- * and NEVER apps don't get them at all.
- */
- final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE };
- long mHeartbeat = 0;
- long mLastHeartbeatTime = sElapsedRealtimeClock.millis();
-
- /**
- * Named indices into the STANDBY_BEATS array, for clarity in referring to
- * specific buckets' bookkeeping.
- */
- public static final int ACTIVE_INDEX = 0;
- public static final int WORKING_INDEX = 1;
- public static final int FREQUENT_INDEX = 2;
- public static final int RARE_INDEX = 3;
- public static final int NEVER_INDEX = 4;
-
- /**
- * Bookkeeping about when jobs last run. We keep our own record in heartbeat time,
- * rather than rely on Usage Stats' timestamps, because heartbeat time can be
- * manipulated for testing purposes and we need job runnability to track that rather
- * than real time.
- *
- * Outer SparseArray slices by user handle; inner map of package name to heartbeat
- * is a HashMap<> rather than ArrayMap<> because we expect O(hundreds) of keys
- * and it will be accessed in a known-hot code path.
- */
- final SparseArray<HashMap<String, Long>> mLastJobHeartbeats = new SparseArray<>();
-
- static final String HEARTBEAT_TAG = "*job.heartbeat*";
- final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener();
-
- // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
-
- private class ConstantsObserver extends ContentObserver {
- private ContentResolver mResolver;
-
- public ConstantsObserver(Handler handler) {
- super(handler);
- }
-
- public void start(ContentResolver resolver) {
- mResolver = resolver;
- mResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
- updateConstants();
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- updateConstants();
- }
-
- private void updateConstants() {
- synchronized (mLock) {
- try {
- mConstants.updateConstantsLocked(Settings.Global.getString(mResolver,
- Settings.Global.JOB_SCHEDULER_CONSTANTS));
- for (int controller = 0; controller < mControllers.size(); controller++) {
- final StateController sc = mControllers.get(controller);
- sc.onConstantsUpdatedLocked();
- }
- } catch (IllegalArgumentException e) {
- // Failed to parse the settings string, log this and move on
- // with defaults.
- Slog.e(TAG, "Bad jobscheduler settings", e);
- }
- }
-
- if (mConstants.USE_HEARTBEATS) {
- // Reset the heartbeat alarm based on the new heartbeat duration
- setNextHeartbeatAlarm();
- }
- }
- }
-
- /**
- * Thermal event received from Thermal Service
- */
- private final class ThermalStatusListener extends IThermalStatusListener.Stub {
- @Override public void onStatusChange(int status) {
- // Throttle for Temperature.THROTTLING_SEVERE and above
- synchronized (mLock) {
- mThermalConstraint = status >= Temperature.THROTTLING_SEVERE;
- }
- onControllerStateChanged();
- }
- }
-
- static class MaxJobCounts {
- private final KeyValueListParser.IntValue mTotal;
- private final KeyValueListParser.IntValue mMaxBg;
- private final KeyValueListParser.IntValue mMinBg;
-
- MaxJobCounts(int totalDefault, String totalKey,
- int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
- mTotal = new KeyValueListParser.IntValue(totalKey, totalDefault);
- mMaxBg = new KeyValueListParser.IntValue(maxBgKey, maxBgDefault);
- mMinBg = new KeyValueListParser.IntValue(minBgKey, minBgDefault);
- }
-
- public void parse(KeyValueListParser parser) {
- mTotal.parse(parser);
- mMaxBg.parse(parser);
- mMinBg.parse(parser);
-
- if (mTotal.getValue() < 1) {
- mTotal.setValue(1);
- } else if (mTotal.getValue() > MAX_JOB_CONTEXTS_COUNT) {
- mTotal.setValue(MAX_JOB_CONTEXTS_COUNT);
- }
-
- if (mMaxBg.getValue() < 1) {
- mMaxBg.setValue(1);
- } else if (mMaxBg.getValue() > mTotal.getValue()) {
- mMaxBg.setValue(mTotal.getValue());
- }
- if (mMinBg.getValue() < 0) {
- mMinBg.setValue(0);
- } else {
- if (mMinBg.getValue() > mMaxBg.getValue()) {
- mMinBg.setValue(mMaxBg.getValue());
- }
- if (mMinBg.getValue() >= mTotal.getValue()) {
- mMinBg.setValue(mTotal.getValue() - 1);
- }
- }
- }
-
- /** Total number of jobs to run simultaneously. */
- public int getMaxTotal() {
- return mTotal.getValue();
- }
-
- /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
- public int getMaxBg() {
- return mMaxBg.getValue();
- }
-
- /**
- * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
- * pending, rather than always running the TOTAL number of FG jobs.
- */
- public int getMinBg() {
- return mMinBg.getValue();
- }
-
- public void dump(PrintWriter pw, String prefix) {
- mTotal.dump(pw, prefix);
- mMaxBg.dump(pw, prefix);
- mMinBg.dump(pw, prefix);
- }
-
- public void dumpProto(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS);
- mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG);
- mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG);
- proto.end(token);
- }
- }
-
- /** {@link MaxJobCounts} for each memory trim level. */
- static class MaxJobCountsPerMemoryTrimLevel {
- public final MaxJobCounts normal;
- public final MaxJobCounts moderate;
- public final MaxJobCounts low;
- public final MaxJobCounts critical;
-
- MaxJobCountsPerMemoryTrimLevel(
- MaxJobCounts normal,
- MaxJobCounts moderate, MaxJobCounts low,
- MaxJobCounts critical) {
- this.normal = normal;
- this.moderate = moderate;
- this.low = low;
- this.critical = critical;
- }
-
- public void dumpProto(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
- moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
- low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
- critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
- proto.end(token);
- }
- }
-
- /**
- * All times are in milliseconds. These constants are kept synchronized with the system
- * global Settings. Any access to this class or its fields should be done while
- * holding the JobSchedulerService.mLock lock.
- */
- public static class Constants {
- // Key names stored in the settings value.
- private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
- private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
- private static final String KEY_MIN_BATTERY_NOT_LOW_COUNT = "min_battery_not_low_count";
- private static final String KEY_MIN_STORAGE_NOT_LOW_COUNT = "min_storage_not_low_count";
- private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
- private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
- private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
- private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT =
- "min_ready_non_active_jobs_count";
- private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS =
- "max_non_active_job_batch_delay_ms";
- private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
- private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
-
- // The following values used to be used on P and below. Do not reuse them.
- private static final String DEPRECATED_KEY_FG_JOB_COUNT = "fg_job_count";
- private static final String DEPRECATED_KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
- private static final String DEPRECATED_KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
- private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
- private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
-
- private static final String KEY_MAX_STANDARD_RESCHEDULE_COUNT
- = "max_standard_reschedule_count";
- private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
- private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
- private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
- private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time";
- private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
- private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
- private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
- private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
- private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
- private static final String KEY_USE_HEARTBEATS = "use_heartbeats";
-
- private static final int DEFAULT_MIN_IDLE_COUNT = 1;
- private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
- private static final int DEFAULT_MIN_BATTERY_NOT_LOW_COUNT = 1;
- private static final int DEFAULT_MIN_STORAGE_NOT_LOW_COUNT = 1;
- private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
- private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
- private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
- private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
- private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
- private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
- private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
- private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE;
- private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
- private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
- private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
- private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L;
- private static final int DEFAULT_STANDBY_WORKING_BEATS = 11; // ~ 2 hours, with 11min beats
- private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 43; // ~ 8 hours
- private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
- private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
- private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
- private static final boolean DEFAULT_USE_HEARTBEATS = false;
-
- /**
- * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
- * early.
- */
- int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT;
- /**
- * Minimum # of charging jobs that must be ready in order to force the JMS to schedule
- * things early.
- */
- int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT;
- /**
- * Minimum # of "battery not low" jobs that must be ready in order to force the JMS to
- * schedule things early.
- */
- int MIN_BATTERY_NOT_LOW_COUNT = DEFAULT_MIN_BATTERY_NOT_LOW_COUNT;
- /**
- * Minimum # of "storage not low" jobs that must be ready in order to force the JMS to
- * schedule things early.
- */
- int MIN_STORAGE_NOT_LOW_COUNT = DEFAULT_MIN_STORAGE_NOT_LOW_COUNT;
- /**
- * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
- * things early. 1 == Run connectivity jobs as soon as ready.
- */
- int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT;
- /**
- * Minimum # of content trigger jobs that must be ready in order to force the JMS to
- * schedule things early.
- */
- int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT;
- /**
- * Minimum # of jobs (with no particular constraints) for which the JMS will be happy
- * running some work early. This (and thus the other min counts) is now set to 1, to
- * prevent any batching at this level. Since we now do batching through doze, that is
- * a much better mechanism.
- */
- int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT;
-
- /**
- * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
- */
- int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT;
-
- /**
- * Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for
- * at least this amount of time.
- */
- long MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
-
- /**
- * This is the job execution factor that is considered to be heavy use of the system.
- */
- float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR;
- /**
- * This is the job execution factor that is considered to be moderate use of the system.
- */
- float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
-
- // Max job counts for screen on / off, for each memory trim level.
- final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
- new MaxJobCountsPerMemoryTrimLevel(
- new MaxJobCounts(
- 8, "max_job_total_on_normal",
- 6, "max_job_max_bg_on_normal",
- 2, "max_job_min_bg_on_normal"),
- new MaxJobCounts(
- 8, "max_job_total_on_moderate",
- 4, "max_job_max_bg_on_moderate",
- 2, "max_job_min_bg_on_moderate"),
- new MaxJobCounts(
- 5, "max_job_total_on_low",
- 1, "max_job_max_bg_on_low",
- 1, "max_job_min_bg_on_low"),
- new MaxJobCounts(
- 5, "max_job_total_on_critical",
- 1, "max_job_max_bg_on_critical",
- 1, "max_job_min_bg_on_critical"));
-
- final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
- new MaxJobCountsPerMemoryTrimLevel(
- new MaxJobCounts(
- 10, "max_job_total_off_normal",
- 6, "max_job_max_bg_off_normal",
- 2, "max_job_min_bg_off_normal"),
- new MaxJobCounts(
- 10, "max_job_total_off_moderate",
- 4, "max_job_max_bg_off_moderate",
- 2, "max_job_min_bg_off_moderate"),
- new MaxJobCounts(
- 5, "max_job_total_off_low",
- 1, "max_job_max_bg_off_low",
- 1, "max_job_min_bg_off_low"),
- new MaxJobCounts(
- 5, "max_job_total_off_critical",
- 1, "max_job_max_bg_off_critical",
- 1, "max_job_min_bg_off_critical"));
-
-
- /** Wait for this long after screen off before increasing the job concurrency. */
- final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
- new KeyValueListParser.IntValue(
- "screen_off_job_concurrency_increase_delay_ms", 30_000);
-
- /**
- * The maximum number of times we allow a job to have itself rescheduled before
- * giving up on it, for standard jobs.
- */
- int MAX_STANDARD_RESCHEDULE_COUNT = DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT;
- /**
- * The maximum number of times we allow a job to have itself rescheduled before
- * giving up on it, for jobs that are executing work.
- */
- int MAX_WORK_RESCHEDULE_COUNT = DEFAULT_MAX_WORK_RESCHEDULE_COUNT;
- /**
- * The minimum backoff time to allow for linear backoff.
- */
- long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME;
- /**
- * The minimum backoff time to allow for exponential backoff.
- */
- long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
- /**
- * How often we recalculate runnability based on apps' standby bucket assignment.
- * This should be prime relative to common time interval lengths such as a quarter-
- * hour or day, so that the heartbeat drifts relative to wall-clock milestones.
- */
- long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
- /**
- * Mapping: standby bucket -> number of heartbeats between each sweep of that
- * bucket's jobs.
- *
- * Bucket assignments as recorded in the JobStatus objects are normalized to be
- * indices into this array, rather than the raw constants used
- * by AppIdleHistory.
- */
- final int[] STANDBY_BEATS = {
- 0,
- DEFAULT_STANDBY_WORKING_BEATS,
- DEFAULT_STANDBY_FREQUENT_BEATS,
- DEFAULT_STANDBY_RARE_BEATS
- };
- /**
- * The fraction of a job's running window that must pass before we
- * consider running it when the network is congested.
- */
- public float CONN_CONGESTION_DELAY_FRAC = DEFAULT_CONN_CONGESTION_DELAY_FRAC;
- /**
- * The fraction of a prefetch job's running window that must pass before
- * we consider matching it against a metered network.
- */
- public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
- /**
- * Whether to use heartbeats or rolling window for quota management. True will use
- * heartbeats, false will use a rolling window.
- */
- public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS;
-
- private final KeyValueListParser mParser = new KeyValueListParser(',');
-
- void updateConstantsLocked(String value) {
- try {
- mParser.setString(value);
- } catch (Exception e) {
- // Failed to parse the settings string, log this and move on
- // with defaults.
- Slog.e(TAG, "Bad jobscheduler settings", e);
- }
-
- MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
- DEFAULT_MIN_IDLE_COUNT);
- MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
- DEFAULT_MIN_CHARGING_COUNT);
- MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
- DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
- MIN_STORAGE_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_STORAGE_NOT_LOW_COUNT,
- DEFAULT_MIN_STORAGE_NOT_LOW_COUNT);
- MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
- DEFAULT_MIN_CONNECTIVITY_COUNT);
- MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
- DEFAULT_MIN_CONTENT_COUNT);
- MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
- DEFAULT_MIN_READY_JOBS_COUNT);
- MIN_READY_NON_ACTIVE_JOBS_COUNT = mParser.getInt(
- KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
- DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT);
- MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = mParser.getLong(
- KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
- DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS);
- HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
- DEFAULT_HEAVY_USE_FACTOR);
- MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
- DEFAULT_MODERATE_USE_FACTOR);
-
- MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser);
- MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser);
- MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser);
- MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser);
-
- MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser);
- MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser);
- MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser);
- MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser);
-
- SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser);
-
- MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
- DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
- MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
- DEFAULT_MAX_WORK_RESCHEDULE_COUNT);
- MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
- DEFAULT_MIN_LINEAR_BACKOFF_TIME);
- MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
- DEFAULT_MIN_EXP_BACKOFF_TIME);
- STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
- DEFAULT_STANDBY_HEARTBEAT_TIME);
- STANDBY_BEATS[WORKING_INDEX] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
- DEFAULT_STANDBY_WORKING_BEATS);
- STANDBY_BEATS[FREQUENT_INDEX] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
- DEFAULT_STANDBY_FREQUENT_BEATS);
- STANDBY_BEATS[RARE_INDEX] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
- DEFAULT_STANDBY_RARE_BEATS);
- CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
- DEFAULT_CONN_CONGESTION_DELAY_FRAC);
- CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
- DEFAULT_CONN_PREFETCH_RELAX_FRAC);
- USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS);
- }
-
- void dump(IndentingPrintWriter pw) {
- pw.println("Settings:");
- pw.increaseIndent();
- pw.printPair(KEY_MIN_IDLE_COUNT, MIN_IDLE_COUNT).println();
- pw.printPair(KEY_MIN_CHARGING_COUNT, MIN_CHARGING_COUNT).println();
- pw.printPair(KEY_MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT).println();
- pw.printPair(KEY_MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT).println();
- pw.printPair(KEY_MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT).println();
- pw.printPair(KEY_MIN_CONTENT_COUNT, MIN_CONTENT_COUNT).println();
- pw.printPair(KEY_MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT).println();
- pw.printPair(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
- MIN_READY_NON_ACTIVE_JOBS_COUNT).println();
- pw.printPair(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
- MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println();
- pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
- pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
-
- MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
- MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
- MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
- MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
-
- MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
- MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
- MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
- MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
-
- SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
-
- pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
- pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
- pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
- pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println();
- pw.printPair(KEY_STANDBY_HEARTBEAT_TIME, STANDBY_HEARTBEAT_TIME).println();
- pw.print("standby_beats={");
- pw.print(STANDBY_BEATS[0]);
- for (int i = 1; i < STANDBY_BEATS.length; i++) {
- pw.print(", ");
- pw.print(STANDBY_BEATS[i]);
- }
- pw.println('}');
- pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
- pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
- pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println();
-
- pw.decreaseIndent();
- }
-
- void dump(ProtoOutputStream proto) {
- proto.write(ConstantsProto.MIN_IDLE_COUNT, MIN_IDLE_COUNT);
- proto.write(ConstantsProto.MIN_CHARGING_COUNT, MIN_CHARGING_COUNT);
- proto.write(ConstantsProto.MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT);
- proto.write(ConstantsProto.MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT);
- proto.write(ConstantsProto.MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT);
- proto.write(ConstantsProto.MIN_CONTENT_COUNT, MIN_CONTENT_COUNT);
- proto.write(ConstantsProto.MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT);
- proto.write(ConstantsProto.MIN_READY_NON_ACTIVE_JOBS_COUNT,
- MIN_READY_NON_ACTIVE_JOBS_COUNT);
- proto.write(ConstantsProto.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
- MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS);
- proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
- proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
-
- MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
- MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
-
- SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
- ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
-
- proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
- proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
- proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
- proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
- proto.write(ConstantsProto.STANDBY_HEARTBEAT_TIME_MS, STANDBY_HEARTBEAT_TIME);
- for (int period : STANDBY_BEATS) {
- proto.write(ConstantsProto.STANDBY_BEATS, period);
- }
- proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
- proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
- proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS);
- }
- }
-
- final Constants mConstants;
- final ConstantsObserver mConstantsObserver;
-
- static final Comparator<JobStatus> mEnqueueTimeComparator = (o1, o2) -> {
- if (o1.enqueueTime < o2.enqueueTime) {
- return -1;
- }
- return o1.enqueueTime > o2.enqueueTime ? 1 : 0;
- };
-
- static <T> void addOrderedItem(ArrayList<T> array, T newItem, Comparator<T> comparator) {
- int where = Collections.binarySearch(array, newItem, comparator);
- if (where < 0) {
- where = ~where;
- }
- array.add(where, newItem);
- }
-
- /**
- * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
- * still clean up. On reinstall the package will have a new uid.
- */
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (DEBUG) {
- Slog.d(TAG, "Receieved: " + action);
- }
- final String pkgName = getPackageName(intent);
- final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-
- if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
- // Purge the app's jobs if the whole package was just disabled. When this is
- // the case the component name will be a bare package name.
- if (pkgName != null && pkgUid != -1) {
- final String[] changedComponents = intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
- if (changedComponents != null) {
- for (String component : changedComponents) {
- if (component.equals(pkgName)) {
- if (DEBUG) {
- Slog.d(TAG, "Package state change: " + pkgName);
- }
- try {
- final int userId = UserHandle.getUserId(pkgUid);
- IPackageManager pm = AppGlobals.getPackageManager();
- final int state = pm.getApplicationEnabledSetting(pkgName, userId);
- if (state == COMPONENT_ENABLED_STATE_DISABLED
- || state == COMPONENT_ENABLED_STATE_DISABLED_USER) {
- if (DEBUG) {
- Slog.d(TAG, "Removing jobs for package " + pkgName
- + " in user " + userId);
- }
- cancelJobsForPackageAndUid(pkgName, pkgUid,
- "app disabled");
- }
- } catch (RemoteException|IllegalArgumentException e) {
- /*
- * IllegalArgumentException means that the package doesn't exist.
- * This arises when PACKAGE_CHANGED broadcast delivery has lagged
- * behind outright uninstall, so by the time we try to act it's gone.
- * We don't need to act on this PACKAGE_CHANGED when this happens;
- * we'll get a PACKAGE_REMOVED later and clean up then.
- *
- * RemoteException can't actually happen; the package manager is
- * running in this same process.
- */
- }
- break;
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "Something in " + pkgName
- + " changed. Reevaluating controller states.");
- }
- synchronized (mLock) {
- for (int c = mControllers.size() - 1; c >= 0; --c) {
- mControllers.get(c).reevaluateStateLocked(pkgUid);
- }
- }
- }
- } else {
- Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
- }
- } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- // If this is an outright uninstall rather than the first half of an
- // app update sequence, cancel the jobs associated with the app.
- if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
- if (DEBUG) {
- Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
- }
- cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
- synchronized (mLock) {
- for (int c = 0; c < mControllers.size(); ++c) {
- mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
- }
- }
- }
- } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- if (DEBUG) {
- Slog.d(TAG, "Removing jobs for user: " + userId);
- }
- cancelJobsForUser(userId);
- synchronized (mLock) {
- for (int c = 0; c < mControllers.size(); ++c) {
- mControllers.get(c).onUserRemovedLocked(userId);
- }
- }
- } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
- // Has this package scheduled any jobs, such that we will take action
- // if it were to be force-stopped?
- if (pkgUid != -1) {
- List<JobStatus> jobsForUid;
- synchronized (mLock) {
- jobsForUid = mJobs.getJobsByUid(pkgUid);
- }
- for (int i = jobsForUid.size() - 1; i >= 0; i--) {
- if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
- if (DEBUG) {
- Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
- + pkgUid + " has jobs");
- }
- setResultCode(Activity.RESULT_OK);
- break;
- }
- }
- }
- } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
- // possible force-stop
- if (pkgUid != -1) {
- if (DEBUG) {
- Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
- }
- cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
- }
- }
- }
- };
-
- private String getPackageName(Intent intent) {
- Uri uri = intent.getData();
- String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
- return pkg;
- }
-
- final private IUidObserver mUidObserver = new IUidObserver.Stub() {
- @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
- mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
- }
-
- @Override public void onUidGone(int uid, boolean disabled) {
- mHandler.obtainMessage(MSG_UID_GONE, uid, disabled ? 1 : 0).sendToTarget();
- }
-
- @Override public void onUidActive(int uid) throws RemoteException {
- mHandler.obtainMessage(MSG_UID_ACTIVE, uid, 0).sendToTarget();
- }
-
- @Override public void onUidIdle(int uid, boolean disabled) {
- mHandler.obtainMessage(MSG_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget();
- }
-
- @Override public void onUidCachedChanged(int uid, boolean cached) {
- }
- };
-
- public Context getTestableContext() {
- return getContext();
- }
-
- public Object getLock() {
- return mLock;
- }
-
- public JobStore getJobStore() {
- return mJobs;
- }
-
- public Constants getConstants() {
- return mConstants;
- }
-
- public boolean isChainedAttributionEnabled() {
- return WorkSource.isChainedBatteryAttributionEnabled(getContext());
- }
-
- @Override
- public void onStartUser(int userHandle) {
- synchronized (mLock) {
- mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
- }
- // Let's kick any outstanding jobs for this user.
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
-
- @Override
- public void onUnlockUser(int userHandle) {
- // Let's kick any outstanding jobs for this user.
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
-
- @Override
- public void onStopUser(int userHandle) {
- synchronized (mLock) {
- mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
- }
- }
-
- /**
- * Return whether an UID is active or idle.
- */
- private boolean isUidActive(int uid) {
- return mAppStateTracker.isUidActiveSynced(uid);
- }
-
- private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
-
- public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
- int userId, String tag) {
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(uId,
- job.getService().getPackageName())) {
- Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
- + " -- package not allowed to start");
- return JobScheduler.RESULT_FAILURE;
- }
- } catch (RemoteException e) {
- }
-
- synchronized (mLock) {
- final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
-
- if (work != null && toCancel != null) {
- // Fast path: we are adding work to an existing job, and the JobInfo is not
- // changing. We can just directly enqueue this work in to the job.
- if (toCancel.getJob().equals(job)) {
-
- toCancel.enqueueWorkLocked(ActivityManager.getService(), work);
-
- // If any of work item is enqueued when the source is in the foreground,
- // exempt the entire job.
- toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
-
- return JobScheduler.RESULT_SUCCESS;
- }
- }
-
- JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
-
- // Give exemption if the source is in the foreground just now.
- // Note if it's a sync job, this method is called on the handler so it's not exactly
- // the state when requestSync() was called, but that should be fine because of the
- // 1 minute foreground grace period.
- jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);
-
- if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
- // Jobs on behalf of others don't apply to the per-app job cap
- if (ENFORCE_MAX_JOBS && packageName == null) {
- if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
- Slog.w(TAG, "Too many jobs for uid " + uId);
- throw new IllegalStateException("Apps may not schedule more than "
- + MAX_JOBS_PER_APP + " distinct jobs");
- }
- }
-
- // This may throw a SecurityException.
- jobStatus.prepareLocked(ActivityManager.getService());
-
- if (work != null) {
- // If work has been supplied, enqueue it into the new job.
- jobStatus.enqueueWorkLocked(ActivityManager.getService(), work);
- }
-
- if (toCancel != null) {
- // Implicitly replaces the existing job record with the new instance
- cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
- } else {
- startTrackingJobLocked(jobStatus, null);
- }
- StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED,
- uId, null, jobStatus.getBatteryName(),
- StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
- JobProtoEnums.STOP_REASON_CANCELLED, jobStatus.getStandbyBucket(),
- jobStatus.getJobId());
-
- // If the job is immediately ready to run, then we can just immediately
- // put it in the pending list and try to schedule it. This is especially
- // important for jobs with a 0 deadline constraint, since they will happen a fair
- // amount, we want to handle them as quickly as possible, and semantically we want to
- // make sure we have started holding the wake lock for the job before returning to
- // the caller.
- // If the job is not yet ready to run, there is nothing more to do -- we are
- // now just waiting for one of its controllers to change state and schedule
- // the job appropriately.
- if (isReadyToBeExecutedLocked(jobStatus)) {
- // This is a new job, we can just immediately put it on the pending
- // list and try to run it.
- mJobPackageTracker.notePending(jobStatus);
- addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
- maybeRunPendingJobsLocked();
- } else {
- evaluateControllerStatesLocked(jobStatus);
- }
- }
- return JobScheduler.RESULT_SUCCESS;
- }
-
- public List<JobInfo> getPendingJobs(int uid) {
- synchronized (mLock) {
- List<JobStatus> jobs = mJobs.getJobsByUid(uid);
- ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.get(i);
- outList.add(job.getJob());
- }
- return outList;
- }
- }
-
- public JobInfo getPendingJob(int uid, int jobId) {
- synchronized (mLock) {
- List<JobStatus> jobs = mJobs.getJobsByUid(uid);
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.get(i);
- if (job.getJobId() == jobId) {
- return job.getJob();
- }
- }
- return null;
- }
- }
-
- void cancelJobsForUser(int userHandle) {
- synchronized (mLock) {
- final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
- for (int i=0; i<jobsForUser.size(); i++) {
- JobStatus toRemove = jobsForUser.get(i);
- cancelJobImplLocked(toRemove, null, "user removed");
- }
- }
- }
-
- private void cancelJobsForNonExistentUsers() {
- UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
- synchronized (mLock) {
- mJobs.removeJobsOfNonUsers(umi.getUserIds());
- }
- }
-
- void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
- if ("android".equals(pkgName)) {
- Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
- return;
- }
- synchronized (mLock) {
- final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
- for (int i = jobsForUid.size() - 1; i >= 0; i--) {
- final JobStatus job = jobsForUid.get(i);
- if (job.getSourcePackageName().equals(pkgName)) {
- cancelJobImplLocked(job, null, reason);
- }
- }
- }
- }
-
- /**
- * Entry point from client to cancel all jobs originating from their uid.
- * This will remove the job from the master list, and cancel the job if it was staged for
- * execution or being executed.
- * @param uid Uid to check against for removal of a job.
- *
- */
- public boolean cancelJobsForUid(int uid, String reason) {
- if (uid == Process.SYSTEM_UID) {
- Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
- return false;
- }
-
- boolean jobsCanceled = false;
- synchronized (mLock) {
- final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
- for (int i=0; i<jobsForUid.size(); i++) {
- JobStatus toRemove = jobsForUid.get(i);
- cancelJobImplLocked(toRemove, null, reason);
- jobsCanceled = true;
- }
- }
- return jobsCanceled;
- }
-
- /**
- * Entry point from client to cancel the job corresponding to the jobId provided.
- * This will remove the job from the master list, and cancel the job if it was staged for
- * execution or being executed.
- * @param uid Uid of the calling client.
- * @param jobId Id of the job, provided at schedule-time.
- */
- public boolean cancelJob(int uid, int jobId, int callingUid) {
- JobStatus toCancel;
- synchronized (mLock) {
- toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
- if (toCancel != null) {
- cancelJobImplLocked(toCancel, null,
- "cancel() called by app, callingUid=" + callingUid
- + " uid=" + uid + " jobId=" + jobId);
- }
- return (toCancel != null);
- }
- }
-
- /**
- * Cancel the given job, stopping it if it's currently executing. If {@code incomingJob}
- * is null, the cancelled job is removed outright from the system. If
- * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
- * currently scheduled jobs.
- */
- private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
- if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
- cancelled.unprepareLocked(ActivityManager.getService());
- stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
- // Remove from pending queue.
- if (mPendingJobs.remove(cancelled)) {
- mJobPackageTracker.noteNonpending(cancelled);
- }
- // Cancel if running.
- stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
- // If this is a replacement, bring in the new version of the job
- if (incomingJob != null) {
- if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
- startTrackingJobLocked(incomingJob, cancelled);
- }
- reportActiveLocked();
- }
-
- void updateUidState(int uid, int procState) {
- synchronized (mLock) {
- if (procState == ActivityManager.PROCESS_STATE_TOP) {
- // Only use this if we are exactly the top app. All others can live
- // with just the foreground priority. This means that persistent processes
- // can never be the top app priority... that is fine.
- mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
- } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_SERVICE);
- } else if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
- mUidPriorityOverride.put(uid, JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE);
- } else {
- mUidPriorityOverride.delete(uid);
- }
- }
- }
-
- @Override
- public void onDeviceIdleStateChanged(boolean deviceIdle) {
- synchronized (mLock) {
- if (DEBUG) {
- Slog.d(TAG, "Doze state changed: " + deviceIdle);
- }
- if (deviceIdle) {
- // When becoming idle, make sure no jobs are actively running,
- // except those using the idle exemption flag.
- for (int i=0; i<mActiveServices.size(); i++) {
- JobServiceContext jsc = mActiveServices.get(i);
- final JobStatus executing = jsc.getRunningJobLocked();
- if (executing != null
- && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
- "cancelled due to doze");
- }
- }
- } else {
- // When coming out of idle, allow thing to start back up.
- if (mReadyToRock) {
- if (mLocalDeviceIdleController != null) {
- if (!mReportedActive) {
- mReportedActive = true;
- mLocalDeviceIdleController.setJobsActive(true);
- }
- }
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
- }
- }
- }
-
- void reportActiveLocked() {
- // active is true if pending queue contains jobs OR some job is running.
- boolean active = mPendingJobs.size() > 0;
- if (mPendingJobs.size() <= 0) {
- for (int i=0; i<mActiveServices.size(); i++) {
- final JobServiceContext jsc = mActiveServices.get(i);
- final JobStatus job = jsc.getRunningJobLocked();
- if (job != null
- && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
- && !job.dozeWhitelisted
- && !job.uidActive) {
- // We will report active if we have a job running and it is not an exception
- // due to being in the foreground or whitelisted.
- active = true;
- break;
- }
- }
- }
-
- if (mReportedActive != active) {
- mReportedActive = active;
- if (mLocalDeviceIdleController != null) {
- mLocalDeviceIdleController.setJobsActive(active);
- }
- }
- }
-
- void reportAppUsage(String packageName, int userId) {
- // This app just transitioned into interactive use or near equivalent, so we should
- // take a look at its job state for feedback purposes.
- }
-
- /**
- * Initializes the system service.
- * <p>
- * Subclasses must define a single argument constructor that accepts the context
- * and passes it to super.
- * </p>
- *
- * @param context The system server context.
- */
- public JobSchedulerService(Context context) {
- super(context);
-
- mLocalPM = LocalServices.getService(PackageManagerInternal.class);
- mActivityManagerInternal = Preconditions.checkNotNull(
- LocalServices.getService(ActivityManagerInternal.class));
-
- mHandler = new JobHandler(context.getMainLooper());
- mConstants = new Constants();
- mConstantsObserver = new ConstantsObserver(mHandler);
- mJobSchedulerStub = new JobSchedulerStub();
-
- mConcurrencyManager = new JobConcurrencyManager(this);
-
- // Set up the app standby bucketing tracker
- mStandbyTracker = new StandbyTracker();
- mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mUsageStats.addAppIdleStateChangeListener(mStandbyTracker);
-
- // The job store needs to call back
- publishLocalService(JobSchedulerInternal.class, new LocalService());
-
- // Initialize the job store and set up any persisted jobs
- mJobs = JobStore.initAndGet(this);
-
- // Create the controllers.
- mControllers = new ArrayList<StateController>();
- mControllers.add(new ConnectivityController(this));
- mControllers.add(new TimeController(this));
- mControllers.add(new IdleController(this));
- mBatteryController = new BatteryController(this);
- mControllers.add(mBatteryController);
- mStorageController = new StorageController(this);
- mControllers.add(mStorageController);
- mControllers.add(new BackgroundJobsController(this));
- mControllers.add(new ContentObserverController(this));
- mDeviceIdleJobsController = new DeviceIdleJobsController(this);
- mControllers.add(mDeviceIdleJobsController);
- mQuotaController = new QuotaController(this);
- mControllers.add(mQuotaController);
-
- // If the job store determined that it can't yet reschedule persisted jobs,
- // we need to start watching the clock.
- if (!mJobs.jobTimesInflatedValid()) {
- Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
- context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
- }
- }
-
- private final BroadcastReceiver mTimeSetReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
- // When we reach clock sanity, recalculate the temporal windows
- // of all affected jobs.
- if (mJobs.clockNowValidToInflate(sSystemClock.millis())) {
- Slog.i(TAG, "RTC now valid; recalculating persisted job windows");
-
- // We've done our job now, so stop watching the time.
- context.unregisterReceiver(this);
-
- // And kick off the work to update the affected jobs, using a secondary
- // thread instead of chugging away here on the main looper thread.
- FgThread.getHandler().post(mJobTimeUpdater);
- }
- }
- }
- };
-
- private final Runnable mJobTimeUpdater = () -> {
- final ArrayList<JobStatus> toRemove = new ArrayList<>();
- final ArrayList<JobStatus> toAdd = new ArrayList<>();
- synchronized (mLock) {
- // Note: we intentionally both look up the existing affected jobs and replace them
- // with recalculated ones inside the same lock lifetime.
- getJobStore().getRtcCorrectedJobsLocked(toAdd, toRemove);
-
- // Now, at each position [i], we have both the existing JobStatus
- // and the one that replaces it.
- final int N = toAdd.size();
- for (int i = 0; i < N; i++) {
- final JobStatus oldJob = toRemove.get(i);
- final JobStatus newJob = toAdd.get(i);
- if (DEBUG) {
- Slog.v(TAG, " replacing " + oldJob + " with " + newJob);
- }
- cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation");
- }
- }
- };
-
- @Override
- public void onStart() {
- publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
- }
-
- @Override
- public void onBootPhase(int phase) {
- if (PHASE_SYSTEM_SERVICES_READY == phase) {
- mConstantsObserver.start(getContext().getContentResolver());
- for (StateController controller : mControllers) {
- controller.onSystemServicesReady();
- }
-
- mAppStateTracker = Preconditions.checkNotNull(
- LocalServices.getService(AppStateTracker.class));
- if (mConstants.USE_HEARTBEATS) {
- setNextHeartbeatAlarm();
- }
-
- // Register br for package removals and user removals.
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
- filter.addDataScheme("package");
- getContext().registerReceiverAsUser(
- mBroadcastReceiver, UserHandle.ALL, filter, null, null);
- final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
- getContext().registerReceiverAsUser(
- mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
- try {
- ActivityManager.getService().registerUidObserver(mUidObserver,
- ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
- | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE,
- ActivityManager.PROCESS_STATE_UNKNOWN, null);
- } catch (RemoteException e) {
- // ignored; both services live in system_server
- }
-
- mConcurrencyManager.onSystemReady();
-
- // Remove any jobs that are not associated with any of the current users.
- cancelJobsForNonExistentUsers();
- // Register thermal callback
- mThermalService = IThermalService.Stub.asInterface(
- ServiceManager.getService(Context.THERMAL_SERVICE));
- if (mThermalService != null) {
- try {
- mThermalService.registerThermalStatusListener(new ThermalStatusListener());
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register thermal callback.", e);
- }
- }
- } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- synchronized (mLock) {
- // Let's go!
- mReadyToRock = true;
- mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
- BatteryStats.SERVICE_NAME));
- mLocalDeviceIdleController
- = LocalServices.getService(DeviceIdleController.LocalService.class);
- // Create the "runners".
- for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
- mActiveServices.add(
- new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
- getContext().getMainLooper()));
- }
- // Attach jobs to their controllers.
- mJobs.forEachJob((job) -> {
- for (int controller = 0; controller < mControllers.size(); controller++) {
- final StateController sc = mControllers.get(controller);
- sc.maybeStartTrackingJobLocked(job, null);
- }
- });
- // GO GO GO!
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
- }
- }
-
- /**
- * Called when we have a job status object that we need to insert in our
- * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
- * about.
- */
- private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if (!jobStatus.isPreparedLocked()) {
- Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
- }
- jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
- final boolean update = mJobs.add(jobStatus);
- if (mReadyToRock) {
- for (int i = 0; i < mControllers.size(); i++) {
- StateController controller = mControllers.get(i);
- if (update) {
- controller.maybeStopTrackingJobLocked(jobStatus, null, true);
- }
- controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
- }
- }
- }
-
- /**
- * Called when we want to remove a JobStatus object that we've finished executing.
- * @return true if the job was removed.
- */
- private boolean stopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
- boolean removeFromPersisted) {
- // Deal with any remaining work items in the old job.
- jobStatus.stopTrackingJobLocked(ActivityManager.getService(), incomingJob);
-
- // Remove from store as well as controllers.
- final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
- if (removed && mReadyToRock) {
- for (int i=0; i<mControllers.size(); i++) {
- StateController controller = mControllers.get(i);
- controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
- }
- }
- return removed;
- }
-
- private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
- for (int i=0; i<mActiveServices.size(); i++) {
- JobServiceContext jsc = mActiveServices.get(i);
- final JobStatus executing = jsc.getRunningJobLocked();
- if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
- jsc.cancelExecutingJobLocked(reason, debugReason);
- return true;
- }
- }
- return false;
- }
-
- /**
- * @param job JobStatus we are querying against.
- * @return Whether or not the job represented by the status object is currently being run or
- * is pending.
- */
- private boolean isCurrentlyActiveLocked(JobStatus job) {
- for (int i=0; i<mActiveServices.size(); i++) {
- JobServiceContext serviceContext = mActiveServices.get(i);
- final JobStatus running = serviceContext.getRunningJobLocked();
- if (running != null && running.matches(job.getUid(), job.getJobId())) {
- return true;
- }
- }
- return false;
- }
-
- void noteJobsPending(List<JobStatus> jobs) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.get(i);
- mJobPackageTracker.notePending(job);
- }
- }
-
- void noteJobsNonpending(List<JobStatus> jobs) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.get(i);
- mJobPackageTracker.noteNonpending(job);
- }
- }
-
- /**
- * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
- * specify an override deadline on a failed job (the failed job will run even though it's not
- * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
- * ready job with {@link JobStatus#getNumFailures()} > 0 will be executed.
- *
- * @param failureToReschedule Provided job status that we will reschedule.
- * @return A newly instantiated JobStatus with the same constraints as the last job except
- * with adjusted timing constraints.
- *
- * @see #maybeQueueReadyJobsForExecutionLocked
- */
- @VisibleForTesting
- JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
- final long elapsedNowMillis = sElapsedRealtimeClock.millis();
- final JobInfo job = failureToReschedule.getJob();
-
- final long initialBackoffMillis = job.getInitialBackoffMillis();
- final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
- long delayMillis;
-
- if (failureToReschedule.hasWorkLocked()) {
- if (backoffAttempts > mConstants.MAX_WORK_RESCHEDULE_COUNT) {
- Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
- + backoffAttempts + " > work limit "
- + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
- return null;
- }
- } else if (backoffAttempts > mConstants.MAX_STANDARD_RESCHEDULE_COUNT) {
- Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
- + backoffAttempts + " > std limit " + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
- return null;
- }
-
- switch (job.getBackoffPolicy()) {
- case JobInfo.BACKOFF_POLICY_LINEAR: {
- long backoff = initialBackoffMillis;
- if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME) {
- backoff = mConstants.MIN_LINEAR_BACKOFF_TIME;
- }
- delayMillis = backoff * backoffAttempts;
- } break;
- default:
- if (DEBUG) {
- Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
- }
- case JobInfo.BACKOFF_POLICY_EXPONENTIAL: {
- long backoff = initialBackoffMillis;
- if (backoff < mConstants.MIN_EXP_BACKOFF_TIME) {
- backoff = mConstants.MIN_EXP_BACKOFF_TIME;
- }
- delayMillis = (long) Math.scalb(backoff, backoffAttempts - 1);
- } break;
- }
- delayMillis =
- Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
- JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(),
- elapsedNowMillis + delayMillis,
- JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
- failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
- if (job.isPeriodic()) {
- newJob.setOriginalLatestRunTimeElapsed(
- failureToReschedule.getOriginalLatestRunTimeElapsed());
- }
- for (int ic=0; ic<mControllers.size(); ic++) {
- StateController controller = mControllers.get(ic);
- controller.rescheduleForFailureLocked(newJob, failureToReschedule);
- }
- return newJob;
- }
-
- /**
- * Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
- * does not cause a job's period to be larger than requested (eg: if the requested period is
- * shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
- * and try to optimize scheduling if the current job finished less than this amount of time to
- * the start of the next period
- */
- private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS;
-
- /** The maximum period a periodic job can have. Anything higher will be clamped down to this. */
- public static final long MAX_ALLOWED_PERIOD_MS = 365 * 24 * 60 * 60 * 1000L;
-
- /**
- * Called after a periodic has executed so we can reschedule it. We take the last execution
- * time of the job to be the time of completion (i.e. the time at which this function is
- * called).
- * <p>This could be inaccurate b/c the job can run for as long as
- * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
- * to underscheduling at least, rather than if we had taken the last execution time to be the
- * start of the execution.
- * <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat
- * tracking as though the job were newly-scheduled.
- * @return A new job representing the execution criteria for this instantiation of the
- * recurring job.
- */
- @VisibleForTesting
- JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
- final long elapsedNow = sElapsedRealtimeClock.millis();
- final long newLatestRuntimeElapsed;
- // Make sure period is in the interval [min_possible_period, max_possible_period].
- final long period = Math.max(JobInfo.getMinPeriodMillis(),
- Math.min(MAX_ALLOWED_PERIOD_MS, periodicToReschedule.getJob().getIntervalMillis()));
- // Make sure flex is in the interval [min_possible_flex, period].
- final long flex = Math.max(JobInfo.getMinFlexMillis(),
- Math.min(period, periodicToReschedule.getJob().getFlexMillis()));
- long rescheduleBuffer = 0;
-
- long olrte = periodicToReschedule.getOriginalLatestRunTimeElapsed();
- if (olrte < 0 || olrte == JobStatus.NO_LATEST_RUNTIME) {
- Slog.wtf(TAG, "Invalid periodic job original latest run time: " + olrte);
- olrte = elapsedNow;
- }
- final long latestRunTimeElapsed = olrte;
-
- final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed);
- if (elapsedNow > latestRunTimeElapsed) {
- // The job ran past its expected run window. Have it count towards the current window
- // and schedule a new job for the next window.
- if (DEBUG) {
- Slog.i(TAG, "Periodic job ran after its intended window.");
- }
- long numSkippedWindows = (diffMs / period) + 1; // +1 to include original window
- if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER,
- (period - flex) / 2)) {
- if (DEBUG) {
- Slog.d(TAG, "Custom flex job ran too close to next window.");
- }
- // For custom flex periods, if the job was run too close to the next window,
- // skip the next window and schedule for the following one.
- numSkippedWindows += 1;
- }
- newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
- } else {
- newLatestRuntimeElapsed = latestRunTimeElapsed + period;
- if (diffMs < PERIODIC_JOB_WINDOW_BUFFER && diffMs < period / 6) {
- // Add a little buffer to the start of the next window so the job doesn't run
- // too soon after this completed one.
- rescheduleBuffer = Math.min(PERIODIC_JOB_WINDOW_BUFFER, period / 6 - diffMs);
- }
- }
-
- if (newLatestRuntimeElapsed < elapsedNow) {
- Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: "
- + newLatestRuntimeElapsed);
- return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
- elapsedNow + period - flex, elapsedNow + period,
- 0 /* backoffAttempt */,
- sSystemClock.millis() /* lastSuccessfulRunTime */,
- periodicToReschedule.getLastFailedRunTime());
- }
-
- final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed
- - Math.min(flex, period - rescheduleBuffer);
-
- if (DEBUG) {
- Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
- newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000
- + "]s");
- }
- return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
- newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
- 0 /* backoffAttempt */,
- sSystemClock.millis() /* lastSuccessfulRunTime */,
- periodicToReschedule.getLastFailedRunTime());
- }
-
- /*
- * We default to "long enough ago that every bucket's jobs are immediately runnable" to
- * avoid starvation of apps in uncommon-use buckets that might arise from repeated
- * reboot behavior.
- */
- long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) {
- // The furthest back in pre-boot time that we need to bother with
- long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX];
- boolean cacheHit = false;
- synchronized (mLock) {
- HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
- if (jobPackages != null) {
- long cachedValue = jobPackages.getOrDefault(packageName, Long.MAX_VALUE);
- if (cachedValue < Long.MAX_VALUE) {
- cacheHit = true;
- heartbeat = cachedValue;
- }
- }
- if (!cacheHit) {
- // We haven't seen it yet; ask usage stats about it
- final long timeSinceJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
- if (timeSinceJob < Long.MAX_VALUE) {
- // Usage stats knows about it from before, so calculate back from that
- // and go from there.
- heartbeat = mHeartbeat - (timeSinceJob / mConstants.STANDBY_HEARTBEAT_TIME);
- }
- // If usage stats returned its "not found" MAX_VALUE, we still have the
- // negative default 'heartbeat' value we established above
- setLastJobHeartbeatLocked(packageName, userId, heartbeat);
- }
- }
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "Last job heartbeat " + heartbeat + " for "
- + packageName + "/" + userId);
- }
- return heartbeat;
- }
-
- long heartbeatWhenJobsLastRun(JobStatus job) {
- return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId());
- }
-
- void setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat) {
- HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
- if (jobPackages == null) {
- jobPackages = new HashMap<>();
- mLastJobHeartbeats.put(userId, jobPackages);
- }
- jobPackages.put(packageName, heartbeat);
- }
-
- // JobCompletedListener implementations.
-
- /**
- * A job just finished executing. We fetch the
- * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
- * whether we want to reschedule we re-add it to the controllers.
- * @param jobStatus Completed job.
- * @param needsReschedule Whether the implementing class should reschedule this job.
- */
- @Override
- public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) {
- if (DEBUG) {
- Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
- }
-
- // If the job wants to be rescheduled, we first need to make the next upcoming
- // job so we can transfer any appropriate state over from the previous job when
- // we stop it.
- final JobStatus rescheduledJob = needsReschedule
- ? getRescheduleJobForFailureLocked(jobStatus) : null;
-
- // Do not write back immediately if this is a periodic job. The job may get lost if system
- // shuts down before it is added back.
- if (!stopTrackingJobLocked(jobStatus, rescheduledJob, !jobStatus.getJob().isPeriodic())) {
- if (DEBUG) {
- Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
- }
- // We still want to check for jobs to execute, because this job may have
- // scheduled a new job under the same job id, and now we can run it.
- mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
- return;
- }
-
- if (rescheduledJob != null) {
- try {
- rescheduledJob.prepareLocked(ActivityManager.getService());
- } catch (SecurityException e) {
- Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledJob);
- }
- startTrackingJobLocked(rescheduledJob, jobStatus);
- } else if (jobStatus.getJob().isPeriodic()) {
- JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
- try {
- rescheduledPeriodic.prepareLocked(ActivityManager.getService());
- } catch (SecurityException e) {
- Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledPeriodic);
- }
- startTrackingJobLocked(rescheduledPeriodic, jobStatus);
- }
- jobStatus.unprepareLocked(ActivityManager.getService());
- reportActiveLocked();
- mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
- }
-
- // StateChangedListener implementations.
-
- /**
- * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
- * some controller's state has changed, so as to run through the list of jobs and start/stop
- * any that are eligible.
- */
- @Override
- public void onControllerStateChanged() {
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
-
- @Override
- public void onRunJobNow(JobStatus jobStatus) {
- mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
- }
-
- final private class JobHandler extends Handler {
-
- public JobHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message message) {
- synchronized (mLock) {
- if (!mReadyToRock) {
- return;
- }
- switch (message.what) {
- case MSG_JOB_EXPIRED: {
- JobStatus runNow = (JobStatus) message.obj;
- // runNow can be null, which is a controller's way of indicating that its
- // state is such that all ready jobs should be run immediately.
- if (runNow != null && isReadyToBeExecutedLocked(runNow)) {
- mJobPackageTracker.notePending(runNow);
- addOrderedItem(mPendingJobs, runNow, mEnqueueTimeComparator);
- } else {
- queueReadyJobsForExecutionLocked();
- }
- } break;
- case MSG_CHECK_JOB:
- if (DEBUG) {
- Slog.d(TAG, "MSG_CHECK_JOB");
- }
- removeMessages(MSG_CHECK_JOB);
- if (mReportedActive) {
- // if jobs are currently being run, queue all ready jobs for execution.
- queueReadyJobsForExecutionLocked();
- } else {
- // Check the list of jobs and run some of them if we feel inclined.
- maybeQueueReadyJobsForExecutionLocked();
- }
- break;
- case MSG_CHECK_JOB_GREEDY:
- if (DEBUG) {
- Slog.d(TAG, "MSG_CHECK_JOB_GREEDY");
- }
- queueReadyJobsForExecutionLocked();
- break;
- case MSG_STOP_JOB:
- cancelJobImplLocked((JobStatus) message.obj, null,
- "app no longer allowed to run");
- break;
-
- case MSG_UID_STATE_CHANGED: {
- final int uid = message.arg1;
- final int procState = message.arg2;
- updateUidState(uid, procState);
- break;
- }
- case MSG_UID_GONE: {
- final int uid = message.arg1;
- final boolean disabled = message.arg2 != 0;
- updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
- if (disabled) {
- cancelJobsForUid(uid, "uid gone");
- }
- synchronized (mLock) {
- mDeviceIdleJobsController.setUidActiveLocked(uid, false);
- }
- break;
- }
- case MSG_UID_ACTIVE: {
- final int uid = message.arg1;
- synchronized (mLock) {
- mDeviceIdleJobsController.setUidActiveLocked(uid, true);
- }
- break;
- }
- case MSG_UID_IDLE: {
- final int uid = message.arg1;
- final boolean disabled = message.arg2 != 0;
- if (disabled) {
- cancelJobsForUid(uid, "app uid idle");
- }
- synchronized (mLock) {
- mDeviceIdleJobsController.setUidActiveLocked(uid, false);
- }
- break;
- }
-
- }
- maybeRunPendingJobsLocked();
- // Don't remove JOB_EXPIRED in case one came along while processing the queue.
- }
- }
- }
-
- private boolean isJobThermalConstrainedLocked(JobStatus job) {
- return mThermalConstraint && job.hasConnectivityConstraint()
- && (evaluateJobPriorityLocked(job) < JobInfo.PRIORITY_FOREGROUND_APP);
- }
-
- private void stopNonReadyActiveJobsLocked() {
- for (int i=0; i<mActiveServices.size(); i++) {
- JobServiceContext serviceContext = mActiveServices.get(i);
- final JobStatus running = serviceContext.getRunningJobLocked();
- if (running == null) {
- continue;
- }
- if (!running.isReady()) {
- serviceContext.cancelExecutingJobLocked(
- JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
- "cancelled due to unsatisfied constraints");
- } else if (isJobThermalConstrainedLocked(running)) {
- serviceContext.cancelExecutingJobLocked(
- JobParameters.REASON_DEVICE_THERMAL,
- "cancelled due to thermal condition");
- }
- }
- }
-
- /**
- * Run through list of jobs and execute all possible - at least one is expired so we do
- * as many as we can.
- */
- private void queueReadyJobsForExecutionLocked() {
- if (DEBUG) {
- Slog.d(TAG, "queuing all ready jobs for execution:");
- }
- noteJobsNonpending(mPendingJobs);
- mPendingJobs.clear();
- stopNonReadyActiveJobsLocked();
- mJobs.forEachJob(mReadyQueueFunctor);
- mReadyQueueFunctor.postProcess();
-
- if (DEBUG) {
- final int queuedJobs = mPendingJobs.size();
- if (queuedJobs == 0) {
- Slog.d(TAG, "No jobs pending.");
- } else {
- Slog.d(TAG, queuedJobs + " jobs queued.");
- }
- }
- }
-
- final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
- final ArrayList<JobStatus> newReadyJobs = new ArrayList<>();
-
- @Override
- public void accept(JobStatus job) {
- if (isReadyToBeExecutedLocked(job)) {
- if (DEBUG) {
- Slog.d(TAG, " queued " + job.toShortString());
- }
- newReadyJobs.add(job);
- } else {
- evaluateControllerStatesLocked(job);
- }
- }
-
- public void postProcess() {
- noteJobsPending(newReadyJobs);
- mPendingJobs.addAll(newReadyJobs);
- if (mPendingJobs.size() > 1) {
- mPendingJobs.sort(mEnqueueTimeComparator);
- }
-
- newReadyJobs.clear();
- }
- }
- private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
-
- /**
- * The state of at least one job has changed. Here is where we could enforce various
- * policies on when we want to execute jobs.
- */
- final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
- int chargingCount;
- int batteryNotLowCount;
- int storageNotLowCount;
- int idleCount;
- int backoffCount;
- int connectivityCount;
- int contentCount;
- int forceBatchedCount;
- int unbatchedCount;
- final List<JobStatus> runnableJobs = new ArrayList<>();
-
- public MaybeReadyJobQueueFunctor() {
- reset();
- }
-
- // Functor method invoked for each job via JobStore.forEachJob()
- @Override
- public void accept(JobStatus job) {
- if (isReadyToBeExecutedLocked(job)) {
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
- job.getJob().getService().getPackageName())) {
- Slog.w(TAG, "Aborting job " + job.getUid() + ":"
- + job.getJob().toString() + " -- package not allowed to start");
- mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
- return;
- }
- } catch (RemoteException e) {
- }
- if (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
- && job.getStandbyBucket() != ACTIVE_INDEX
- && (job.getFirstForceBatchedTimeElapsed() == 0
- || sElapsedRealtimeClock.millis() - job.getFirstForceBatchedTimeElapsed()
- < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS)) {
- // Force batching non-ACTIVE jobs. Don't include them in the other counts.
- forceBatchedCount++;
- if (job.getFirstForceBatchedTimeElapsed() == 0) {
- job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis());
- }
- } else {
- unbatchedCount++;
- if (job.getNumFailures() > 0) {
- backoffCount++;
- }
- if (job.hasIdleConstraint()) {
- idleCount++;
- }
- if (job.hasConnectivityConstraint()) {
- connectivityCount++;
- }
- if (job.hasChargingConstraint()) {
- chargingCount++;
- }
- if (job.hasBatteryNotLowConstraint()) {
- batteryNotLowCount++;
- }
- if (job.hasStorageNotLowConstraint()) {
- storageNotLowCount++;
- }
- if (job.hasContentTriggerConstraint()) {
- contentCount++;
- }
- }
- runnableJobs.add(job);
- } else {
- evaluateControllerStatesLocked(job);
- }
- }
-
- public void postProcess() {
- if (backoffCount > 0 ||
- idleCount >= mConstants.MIN_IDLE_COUNT ||
- connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
- chargingCount >= mConstants.MIN_CHARGING_COUNT ||
- batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
- storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT ||
- contentCount >= mConstants.MIN_CONTENT_COUNT ||
- forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT ||
- (unbatchedCount > 0 && (unbatchedCount + forceBatchedCount)
- >= mConstants.MIN_READY_JOBS_COUNT)) {
- if (DEBUG) {
- Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
- }
- noteJobsPending(runnableJobs);
- mPendingJobs.addAll(runnableJobs);
- if (mPendingJobs.size() > 1) {
- mPendingJobs.sort(mEnqueueTimeComparator);
- }
- } else {
- if (DEBUG) {
- Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
- }
- }
-
- // Be ready for next time
- reset();
- }
-
- @VisibleForTesting
- void reset() {
- chargingCount = 0;
- idleCount = 0;
- backoffCount = 0;
- connectivityCount = 0;
- batteryNotLowCount = 0;
- storageNotLowCount = 0;
- contentCount = 0;
- forceBatchedCount = 0;
- unbatchedCount = 0;
- runnableJobs.clear();
- }
- }
- private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
-
- private void maybeQueueReadyJobsForExecutionLocked() {
- if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
-
- noteJobsNonpending(mPendingJobs);
- mPendingJobs.clear();
- stopNonReadyActiveJobsLocked();
- mJobs.forEachJob(mMaybeQueueFunctor);
- mMaybeQueueFunctor.postProcess();
- }
-
- /**
- * Heartbeat tracking. The heartbeat alarm is intentionally non-wakeup.
- */
- class HeartbeatAlarmListener implements AlarmManager.OnAlarmListener {
-
- @Override
- public void onAlarm() {
- synchronized (mLock) {
- final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime;
- final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME;
- if (beatsElapsed > 0) {
- mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME;
- advanceHeartbeatLocked(beatsElapsed);
- }
- }
- setNextHeartbeatAlarm();
- }
- }
-
- // Intentionally does not touch the alarm timing
- void advanceHeartbeatLocked(long beatsElapsed) {
- if (!mConstants.USE_HEARTBEATS) {
- return;
- }
- mHeartbeat += beatsElapsed;
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed
- + " to " + mHeartbeat);
- }
- // Don't update ACTIVE or NEVER bucket milestones. Note that mHeartbeat
- // will be equal to mNextBucketHeartbeat[bucket] for one beat, during which
- // new jobs scheduled by apps in that bucket will be permitted to run
- // immediately.
- boolean didAdvanceBucket = false;
- for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) {
- // Did we reach or cross a bucket boundary?
- if (mHeartbeat >= mNextBucketHeartbeat[i]) {
- didAdvanceBucket = true;
- }
- while (mHeartbeat > mNextBucketHeartbeat[i]) {
- mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i];
- }
- if (DEBUG_STANDBY) {
- Slog.v(TAG, " Bucket " + i + " next heartbeat "
- + mNextBucketHeartbeat[i]);
- }
- }
-
- if (didAdvanceBucket) {
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "Hit bucket boundary; reevaluating job runnability");
- }
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
- }
-
- void setNextHeartbeatAlarm() {
- final long heartbeatLength;
- synchronized (mLock) {
- if (!mConstants.USE_HEARTBEATS) {
- return;
- }
- heartbeatLength = mConstants.STANDBY_HEARTBEAT_TIME;
- }
- final long now = sElapsedRealtimeClock.millis();
- final long nextBeatOrdinal = (now + heartbeatLength) / heartbeatLength;
- final long nextHeartbeat = nextBeatOrdinal * heartbeatLength;
- if (DEBUG_STANDBY) {
- Slog.i(TAG, "Setting heartbeat alarm for " + nextHeartbeat
- + " = " + TimeUtils.formatDuration(nextHeartbeat - now));
- }
- AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
- am.setExact(AlarmManager.ELAPSED_REALTIME, nextHeartbeat,
- HEARTBEAT_TAG, mHeartbeatAlarm, mHandler);
- }
-
- /** Returns true if both the calling and source users for the job are started. */
- private boolean areUsersStartedLocked(final JobStatus job) {
- boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
- if (job.getUserId() == job.getSourceUserId()) {
- return sourceStarted;
- }
- return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId());
- }
-
- /**
- * Criteria for moving a job into the pending queue:
- * - It's ready.
- * - It's not pending.
- * - It's not already running on a JSC.
- * - The user that requested the job is running.
- * - The job's standby bucket has come due to be runnable.
- * - The component is enabled and runnable.
- */
- @VisibleForTesting
- boolean isReadyToBeExecutedLocked(JobStatus job) {
- final boolean jobReady = job.isReady();
-
- if (DEBUG) {
- Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
- + " ready=" + jobReady);
- }
-
- // This is a condition that is very likely to be false (most jobs that are
- // scheduled are sitting there, not ready yet) and very cheap to check (just
- // a few conditions on data in JobStatus).
- if (!jobReady) {
- if (job.getSourcePackageName().equals("android.jobscheduler.cts.jobtestapp")) {
- Slog.v(TAG, " NOT READY: " + job);
- }
- return false;
- }
-
- final boolean jobExists = mJobs.containsJob(job);
-
- final boolean userStarted = areUsersStartedLocked(job);
-
- if (DEBUG) {
- Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
- + " exists=" + jobExists + " userStarted=" + userStarted);
- }
-
- // These are also fairly cheap to check, though they typically will not
- // be conditions we fail.
- if (!jobExists || !userStarted) {
- return false;
- }
-
- if (isJobThermalConstrainedLocked(job)) {
- return false;
- }
-
- final boolean jobPending = mPendingJobs.contains(job);
- final boolean jobActive = isCurrentlyActiveLocked(job);
-
- if (DEBUG) {
- Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
- + " pending=" + jobPending + " active=" + jobActive);
- }
-
- // These can be a little more expensive (especially jobActive, since we need to
- // go through the array of all potentially active jobs), so we are doing them
- // later... but still before checking with the package manager!
- if (jobPending || jobActive) {
- return false;
- }
-
- if (mConstants.USE_HEARTBEATS) {
- // If the app is in a non-active standby bucket, make sure we've waited
- // an appropriate amount of time since the last invocation. During device-
- // wide parole, standby bucketing is ignored.
- //
- // Jobs in 'active' apps are not subject to standby, nor are jobs that are
- // specifically marked as exempt.
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
- + " parole=" + mInParole + " active=" + job.uidActive
- + " exempt=" + job.getJob().isExemptedFromAppStandby());
- }
- if (!mInParole
- && !job.uidActive
- && !job.getJob().isExemptedFromAppStandby()) {
- final int bucket = job.getStandbyBucket();
- if (DEBUG_STANDBY) {
- Slog.v(TAG, " bucket=" + bucket + " heartbeat=" + mHeartbeat
- + " next=" + mNextBucketHeartbeat[bucket]);
- }
- if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
- // Only skip this job if the app is still waiting for the end of its nominal
- // bucket interval. Once it's waited that long, we let it go ahead and clear.
- // The final (NEVER) bucket is special; we never age those apps' jobs into
- // runnability.
- final long appLastRan = heartbeatWhenJobsLastRun(job);
- if (bucket >= mConstants.STANDBY_BEATS.length
- || (mHeartbeat > appLastRan
- && mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) {
- if (job.getWhenStandbyDeferred() == 0) {
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
- + (appLastRan + mConstants.STANDBY_BEATS[bucket])
- + " for " + job);
- }
- job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
- }
- return false;
- } else {
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "Bucket deferred job aged into runnability at "
- + mHeartbeat + " : " + job);
- }
- }
- }
- }
- }
-
- // The expensive check: validate that the defined package+service is
- // still present & viable.
- return isComponentUsable(job);
- }
-
- private boolean isComponentUsable(@NonNull JobStatus job) {
- final ServiceInfo service;
- try {
- // TODO: cache result until we're notified that something in the package changed.
- service = AppGlobals.getPackageManager().getServiceInfo(
- job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- job.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
-
- if (service == null) {
- if (DEBUG) {
- Slog.v(TAG, "isComponentUsable: " + job.toShortString()
- + " component not present");
- }
- return false;
- }
-
- // Everything else checked out so far, so this is the final yes/no check
- final boolean appIsBad = mActivityManagerInternal.isAppBad(service.applicationInfo);
- if (DEBUG && appIsBad) {
- Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable");
- }
- return !appIsBad;
- }
-
- @VisibleForTesting
- void evaluateControllerStatesLocked(final JobStatus job) {
- for (int c = mControllers.size() - 1; c >= 0; --c) {
- final StateController sc = mControllers.get(c);
- sc.evaluateStateLocked(job);
- }
- }
-
- /**
- * Returns true if non-job constraint components are in place -- if job.isReady() returns true
- * and this method returns true, then the job is ready to be executed.
- */
- public boolean areComponentsInPlaceLocked(JobStatus job) {
- // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same
- // conditions.
-
- final boolean jobExists = mJobs.containsJob(job);
- final boolean userStarted = areUsersStartedLocked(job);
-
- if (DEBUG) {
- Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
- + " exists=" + jobExists + " userStarted=" + userStarted);
- }
-
- // These are also fairly cheap to check, though they typically will not
- // be conditions we fail.
- if (!jobExists || !userStarted) {
- return false;
- }
-
- if (isJobThermalConstrainedLocked(job)) {
- return false;
- }
-
- // Job pending/active doesn't affect the readiness of a job.
-
- // Skipping the heartbeat check as this will only come into play when using the rolling
- // window quota management system.
-
- // The expensive check: validate that the defined package+service is
- // still present & viable.
- return isComponentUsable(job);
- }
-
- /** Returns the maximum amount of time this job could run for. */
- public long getMaxJobExecutionTimeMs(JobStatus job) {
- synchronized (mLock) {
- if (mConstants.USE_HEARTBEATS) {
- return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
- }
- return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
- JobServiceContext.EXECUTING_TIMESLICE_MILLIS);
- }
- }
-
- /**
- * Reconcile jobs in the pending queue against available execution contexts.
- * A controller can force a job into the pending queue even if it's already running, but
- * here is where we decide whether to actually execute it.
- */
- void maybeRunPendingJobsLocked() {
- if (DEBUG) {
- Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
- }
- mConcurrencyManager.assignJobsToContextsLocked();
- reportActiveLocked();
- }
-
- private int adjustJobPriority(int curPriority, JobStatus job) {
- if (curPriority < JobInfo.PRIORITY_TOP_APP) {
- float factor = mJobPackageTracker.getLoadFactor(job);
- if (factor >= mConstants.HEAVY_USE_FACTOR) {
- curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
- } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
- curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
- }
- }
- return curPriority;
- }
-
- int evaluateJobPriorityLocked(JobStatus job) {
- int priority = job.getPriority();
- if (priority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE) {
- return adjustJobPriority(priority, job);
- }
- int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
- if (override != 0) {
- return adjustJobPriority(override, job);
- }
- return adjustJobPriority(priority, job);
- }
-
- final class LocalService implements JobSchedulerInternal {
-
- /**
- * The current bucket heartbeat ordinal
- */
- public long currentHeartbeat() {
- return getCurrentHeartbeat();
- }
-
- /**
- * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
- */
- public long nextHeartbeatForBucket(int bucket) {
- synchronized (mLock) {
- return mNextBucketHeartbeat[bucket];
- }
- }
-
- /**
- * Heartbeat ordinal for the given app. This is typically the heartbeat at which
- * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
- * jobs in a long time is immediately runnable even if the app is bucketed into
- * an infrequent time allocation.
- */
- public long baseHeartbeatForApp(String packageName, @UserIdInt int userId,
- final int appStandbyBucket) {
- if (appStandbyBucket == 0 ||
- appStandbyBucket >= mConstants.STANDBY_BEATS.length) {
- // ACTIVE => everything can be run right away
- // NEVER => we won't run them anyway, so let them go in the future
- // as soon as the app enters normal use
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "Base heartbeat forced ZERO for new job in "
- + packageName + "/" + userId);
- }
- return 0;
- }
-
- final long baseHeartbeat = heartbeatWhenJobsLastRun(packageName, userId);
- if (DEBUG_STANDBY) {
- Slog.v(TAG, "Base heartbeat " + baseHeartbeat + " for new job in "
- + packageName + "/" + userId);
- }
- return baseHeartbeat;
- }
-
- public void noteJobStart(String packageName, int userId) {
- synchronized (mLock) {
- setLastJobHeartbeatLocked(packageName, userId, mHeartbeat);
- }
- }
-
- /**
- * Returns a list of all pending jobs. A running job is not considered pending. Periodic
- * jobs are always considered pending.
- */
- @Override
- public List<JobInfo> getSystemScheduledPendingJobs() {
- synchronized (mLock) {
- final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
- mJobs.forEachJob(Process.SYSTEM_UID, (job) -> {
- if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
- pendingJobs.add(job.getJob());
- }
- });
- return pendingJobs;
- }
- }
-
- @Override
- public void cancelJobsForUid(int uid, String reason) {
- JobSchedulerService.this.cancelJobsForUid(uid, reason);
- }
-
- @Override
- public void addBackingUpUid(int uid) {
- synchronized (mLock) {
- // No need to actually do anything here, since for a full backup the
- // activity manager will kill the process which will kill the job (and
- // cause it to restart, but now it can't run).
- mBackingUpUids.put(uid, uid);
- }
- }
-
- @Override
- public void removeBackingUpUid(int uid) {
- synchronized (mLock) {
- mBackingUpUids.delete(uid);
- // If there are any jobs for this uid, we need to rebuild the pending list
- // in case they are now ready to run.
- if (mJobs.countJobsForUid(uid) > 0) {
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
- }
- }
-
- @Override
- public void clearAllBackingUpUids() {
- synchronized (mLock) {
- if (mBackingUpUids.size() > 0) {
- mBackingUpUids.clear();
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- }
- }
- }
-
- @Override
- public void reportAppUsage(String packageName, int userId) {
- JobSchedulerService.this.reportAppUsage(packageName, userId);
- }
-
- @Override
- public JobStorePersistStats getPersistStats() {
- synchronized (mLock) {
- return new JobStorePersistStats(mJobs.getPersistStats());
- }
- }
- }
-
- /**
- * Tracking of app assignments to standby buckets
- */
- final class StandbyTracker extends AppIdleStateChangeListener {
-
- // AppIdleStateChangeListener interface for live updates
-
- @Override
- public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
- boolean idle, int bucket, int reason) {
- // QuotaController handles this now.
- }
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- if (DEBUG_STANDBY) {
- Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- mInParole = isParoleOn;
- }
-
- @Override
- public void onUserInteractionStarted(String packageName, int userId) {
- final int uid = mLocalPM.getPackageUid(packageName,
- PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
- if (uid < 0) {
- // Quietly ignore; the case is already logged elsewhere
- return;
- }
-
- long sinceLast = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
- if (sinceLast > 2 * DateUtils.DAY_IN_MILLIS) {
- // Too long ago, not worth logging
- sinceLast = 0L;
- }
- final DeferredJobCounter counter = new DeferredJobCounter();
- synchronized (mLock) {
- mJobs.forEachJobForSourceUid(uid, counter);
- }
- if (counter.numDeferred() > 0 || sinceLast > 0) {
- BatteryStatsInternal mBatteryStatsInternal = LocalServices.getService
- (BatteryStatsInternal.class);
- mBatteryStatsInternal.noteJobsDeferred(uid, counter.numDeferred(), sinceLast);
- StatsLog.write_non_chained(StatsLog.DEFERRED_JOB_STATS_REPORTED, uid, null,
- counter.numDeferred(), sinceLast);
- }
- }
- }
-
- static class DeferredJobCounter implements Consumer<JobStatus> {
- private int mDeferred = 0;
-
- public int numDeferred() {
- return mDeferred;
- }
-
- @Override
- public void accept(JobStatus job) {
- if (job.getWhenStandbyDeferred() > 0) {
- mDeferred++;
- }
- }
- }
-
- public static int standbyBucketToBucketIndex(int bucket) {
- // Normalize AppStandby constants to indices into our bookkeeping
- if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) return NEVER_INDEX;
- else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) return RARE_INDEX;
- else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) return FREQUENT_INDEX;
- else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) return WORKING_INDEX;
- else return ACTIVE_INDEX;
- }
-
- // Static to support external callers
- public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) {
- UsageStatsManagerInternal usageStats = LocalServices.getService(
- UsageStatsManagerInternal.class);
- int bucket = usageStats != null
- ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow)
- : 0;
-
- bucket = standbyBucketToBucketIndex(bucket);
-
- if (DEBUG_STANDBY) {
- Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket);
- }
- return bucket;
- }
-
- /**
- * Binder stub trampoline implementation
- */
- final class JobSchedulerStub extends IJobScheduler.Stub {
- /** Cache determination of whether a given app can persist jobs
- * key is uid of the calling app; value is undetermined/true/false
- */
- private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
-
- // Enforce that only the app itself (or shared uid participant) can schedule a
- // job that runs one of the app's services, as well as verifying that the
- // named service properly requires the BIND_JOB_SERVICE permission
- private void enforceValidJobRequest(int uid, JobInfo job) {
- final IPackageManager pm = AppGlobals.getPackageManager();
- final ComponentName service = job.getService();
- try {
- ServiceInfo si = pm.getServiceInfo(service,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.getUserId(uid));
- if (si == null) {
- throw new IllegalArgumentException("No such service " + service);
- }
- if (si.applicationInfo.uid != uid) {
- throw new IllegalArgumentException("uid " + uid +
- " cannot schedule job in " + service.getPackageName());
- }
- if (!JobService.PERMISSION_BIND.equals(si.permission)) {
- throw new IllegalArgumentException("Scheduled service " + service
- + " does not require android.permission.BIND_JOB_SERVICE permission");
- }
- } catch (RemoteException e) {
- // Can't happen; the Package Manager is in this same process
- }
- }
-
- private boolean canPersistJobs(int pid, int uid) {
- // If we get this far we're good to go; all we need to do now is check
- // whether the app is allowed to persist its scheduled work.
- final boolean canPersist;
- synchronized (mPersistCache) {
- Boolean cached = mPersistCache.get(uid);
- if (cached != null) {
- canPersist = cached.booleanValue();
- } else {
- // Persisting jobs is tantamount to running at boot, so we permit
- // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
- // permission
- int result = getContext().checkPermission(
- android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
- canPersist = (result == PackageManager.PERMISSION_GRANTED);
- mPersistCache.put(uid, canPersist);
- }
- }
- return canPersist;
- }
-
- private void validateJobFlags(JobInfo job, int callingUid) {
- if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
- getContext().enforceCallingOrSelfPermission(
- android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
- }
- if ((job.getFlags() & JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY) != 0) {
- if (callingUid != Process.SYSTEM_UID) {
- throw new SecurityException("Job has invalid flags");
- }
- if (job.isPeriodic()) {
- Slog.wtf(TAG, "Periodic jobs mustn't have"
- + " FLAG_EXEMPT_FROM_APP_STANDBY. Job=" + job);
- }
- }
- }
-
- // IJobScheduler implementation
- @Override
- public int schedule(JobInfo job) throws RemoteException {
- if (DEBUG) {
- Slog.d(TAG, "Scheduling job: " + job.toString());
- }
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
-
- enforceValidJobRequest(uid, job);
- if (job.isPersisted()) {
- if (!canPersistJobs(pid, uid)) {
- throw new IllegalArgumentException("Error: requested job be persisted without"
- + " holding RECEIVE_BOOT_COMPLETED permission.");
- }
- }
-
- validateJobFlags(job, uid);
-
- long ident = Binder.clearCallingIdentity();
- try {
- return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
- null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- // IJobScheduler implementation
- @Override
- public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
- if (DEBUG) {
- Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
- }
- final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
-
- enforceValidJobRequest(uid, job);
- if (job.isPersisted()) {
- throw new IllegalArgumentException("Can't enqueue work for persisted jobs");
- }
- if (work == null) {
- throw new NullPointerException("work is null");
- }
-
- validateJobFlags(job, uid);
-
- long ident = Binder.clearCallingIdentity();
- try {
- return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
- null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
- throws RemoteException {
- final int callerUid = Binder.getCallingUid();
- if (DEBUG) {
- Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
- + " on behalf of " + packageName + "/");
- }
-
- if (packageName == null) {
- throw new NullPointerException("Must specify a package for scheduleAsPackage()");
- }
-
- int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS);
- if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller uid " + callerUid
- + " not permitted to schedule jobs for other apps");
- }
-
- validateJobFlags(job, callerUid);
-
- long ident = Binder.clearCallingIdentity();
- try {
- return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
- packageName, userId, tag);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException {
- final int uid = Binder.getCallingUid();
-
- long ident = Binder.clearCallingIdentity();
- try {
- return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public JobInfo getPendingJob(int jobId) throws RemoteException {
- final int uid = Binder.getCallingUid();
-
- long ident = Binder.clearCallingIdentity();
- try {
- return JobSchedulerService.this.getPendingJob(uid, jobId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public void cancelAll() throws RemoteException {
- final int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
- try {
- JobSchedulerService.this.cancelJobsForUid(uid,
- "cancelAll() called by app, callingUid=" + uid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public void cancel(int jobId) throws RemoteException {
- final int uid = Binder.getCallingUid();
-
- long ident = Binder.clearCallingIdentity();
- try {
- JobSchedulerService.this.cancelJob(uid, jobId, uid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * "dumpsys" infrastructure
- */
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
-
- int filterUid = -1;
- boolean proto = false;
- if (!ArrayUtils.isEmpty(args)) {
- int opti = 0;
- while (opti < args.length) {
- String arg = args[opti];
- if ("-h".equals(arg)) {
- dumpHelp(pw);
- return;
- } else if ("-a".equals(arg)) {
- // Ignore, we always dump all.
- } else if ("--proto".equals(arg)) {
- proto = true;
- } else if (arg.length() > 0 && arg.charAt(0) == '-') {
- pw.println("Unknown option: " + arg);
- return;
- } else {
- break;
- }
- opti++;
- }
- if (opti < args.length) {
- String pkg = args[opti];
- try {
- filterUid = getContext().getPackageManager().getPackageUid(pkg,
- PackageManager.MATCH_ANY_USER);
- } catch (NameNotFoundException ignored) {
- pw.println("Invalid package: " + pkg);
- return;
- }
- }
- }
-
- final long identityToken = Binder.clearCallingIdentity();
- try {
- if (proto) {
- JobSchedulerService.this.dumpInternalProto(fd, filterUid);
- } else {
- JobSchedulerService.this.dumpInternal(new IndentingPrintWriter(pw, " "),
- filterUid);
- }
- } finally {
- Binder.restoreCallingIdentity(identityToken);
- }
- }
-
- @Override
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
- (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
- this, in, out, err, args, callback, resultReceiver);
- }
-
- /**
- * <b>For internal system user only!</b>
- * Returns a list of all currently-executing jobs.
- */
- @Override
- public List<JobInfo> getStartedJobs() {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID) {
- throw new SecurityException(
- "getStartedJobs() is system internal use only.");
- }
-
- final ArrayList<JobInfo> runningJobs;
-
- synchronized (mLock) {
- runningJobs = new ArrayList<>(mActiveServices.size());
- for (JobServiceContext jsc : mActiveServices) {
- final JobStatus job = jsc.getRunningJobLocked();
- if (job != null) {
- runningJobs.add(job.getJob());
- }
- }
- }
-
- return runningJobs;
- }
-
- /**
- * <b>For internal system user only!</b>
- * Returns a snapshot of the state of all jobs known to the system.
- *
- * <p class="note">This is a slow operation, so it should be called sparingly.
- */
- @Override
- public ParceledListSlice<JobSnapshot> getAllJobSnapshots() {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID) {
- throw new SecurityException(
- "getAllJobSnapshots() is system internal use only.");
- }
- synchronized (mLock) {
- final ArrayList<JobSnapshot> snapshots = new ArrayList<>(mJobs.size());
- mJobs.forEachJob((job) -> snapshots.add(
- new JobSnapshot(job.getJob(), job.getSatisfiedConstraintFlags(),
- isReadyToBeExecutedLocked(job))));
- return new ParceledListSlice<>(snapshots);
- }
- }
- };
-
- // Shell command infrastructure: run the given job immediately
- int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
- if (DEBUG) {
- Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
- + " " + jobId + " f=" + force);
- }
-
- try {
- final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
- userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
- if (uid < 0) {
- return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
- }
-
- synchronized (mLock) {
- final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
- if (js == null) {
- return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
- }
-
- js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
- if (!js.isConstraintsSatisfied()) {
- js.overrideState = 0;
- return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
- }
-
- queueReadyJobsForExecutionLocked();
- maybeRunPendingJobsLocked();
- }
- } catch (RemoteException e) {
- // can't happen
- }
- return 0;
- }
-
- // Shell command infrastructure: immediately timeout currently executing jobs
- int executeTimeoutCommand(PrintWriter pw, String pkgName, int userId,
- boolean hasJobId, int jobId) {
- if (DEBUG) {
- Slog.v(TAG, "executeTimeoutCommand(): " + pkgName + "/" + userId + " " + jobId);
- }
-
- synchronized (mLock) {
- boolean foundSome = false;
- for (int i=0; i<mActiveServices.size(); i++) {
- final JobServiceContext jc = mActiveServices.get(i);
- final JobStatus js = jc.getRunningJobLocked();
- if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
- foundSome = true;
- pw.print("Timing out: ");
- js.printUniqueId(pw);
- pw.print(" ");
- pw.println(js.getServiceComponent().flattenToShortString());
- }
- }
- if (!foundSome) {
- pw.println("No matching executing jobs found.");
- }
- }
- return 0;
- }
-
- // Shell command infrastructure: cancel a scheduled job
- int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
- boolean hasJobId, int jobId) {
- if (DEBUG) {
- Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
- }
-
- int pkgUid = -1;
- try {
- IPackageManager pm = AppGlobals.getPackageManager();
- pkgUid = pm.getPackageUid(pkgName, 0, userId);
- } catch (RemoteException e) { /* can't happen */ }
-
- if (pkgUid < 0) {
- pw.println("Package " + pkgName + " not found.");
- return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
- }
-
- if (!hasJobId) {
- pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
- if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
- pw.println("No matching jobs found.");
- }
- } else {
- pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
- if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
- pw.println("No matching job found.");
- }
- }
-
- return 0;
- }
-
- void setMonitorBattery(boolean enabled) {
- synchronized (mLock) {
- if (mBatteryController != null) {
- mBatteryController.getTracker().setMonitorBatteryLocked(enabled);
- }
- }
- }
-
- int getBatterySeq() {
- synchronized (mLock) {
- return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1;
- }
- }
-
- boolean getBatteryCharging() {
- synchronized (mLock) {
- return mBatteryController != null
- ? mBatteryController.getTracker().isOnStablePower() : false;
- }
- }
-
- boolean getBatteryNotLow() {
- synchronized (mLock) {
- return mBatteryController != null
- ? mBatteryController.getTracker().isBatteryNotLow() : false;
- }
- }
-
- int getStorageSeq() {
- synchronized (mLock) {
- return mStorageController != null ? mStorageController.getTracker().getSeq() : -1;
- }
- }
-
- boolean getStorageNotLow() {
- synchronized (mLock) {
- return mStorageController != null
- ? mStorageController.getTracker().isStorageNotLow() : false;
- }
- }
-
- long getCurrentHeartbeat() {
- synchronized (mLock) {
- return mHeartbeat;
- }
- }
-
- // Shell command infrastructure
- int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
- try {
- final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
- userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
- if (uid < 0) {
- pw.print("unknown("); pw.print(pkgName); pw.println(")");
- return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
- }
-
- synchronized (mLock) {
- final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
- if (DEBUG) Slog.d(TAG, "get-job-state " + uid + "/" + jobId + ": " + js);
- if (js == null) {
- pw.print("unknown("); UserHandle.formatUid(pw, uid);
- pw.print("/jid"); pw.print(jobId); pw.println(")");
- return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
- }
-
- boolean printed = false;
- if (mPendingJobs.contains(js)) {
- pw.print("pending");
- printed = true;
- }
- if (isCurrentlyActiveLocked(js)) {
- if (printed) {
- pw.print(" ");
- }
- printed = true;
- pw.println("active");
- }
- if (!ArrayUtils.contains(mStartedUsers, js.getUserId())) {
- if (printed) {
- pw.print(" ");
- }
- printed = true;
- pw.println("user-stopped");
- }
- if (!ArrayUtils.contains(mStartedUsers, js.getSourceUserId())) {
- if (printed) {
- pw.print(" ");
- }
- printed = true;
- pw.println("source-user-stopped");
- }
- if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) {
- if (printed) {
- pw.print(" ");
- }
- printed = true;
- pw.println("backing-up");
- }
- boolean componentPresent = false;
- try {
- componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
- js.getServiceComponent(),
- PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- js.getUserId()) != null);
- } catch (RemoteException e) {
- }
- if (!componentPresent) {
- if (printed) {
- pw.print(" ");
- }
- printed = true;
- pw.println("no-component");
- }
- if (js.isReady()) {
- if (printed) {
- pw.print(" ");
- }
- printed = true;
- pw.println("ready");
- }
- if (!printed) {
- pw.print("waiting");
- }
- pw.println();
- }
- } catch (RemoteException e) {
- // can't happen
- }
- return 0;
- }
-
- // Shell command infrastructure
- int executeHeartbeatCommand(PrintWriter pw, int numBeats) {
- if (numBeats < 1) {
- pw.println(getCurrentHeartbeat());
- return 0;
- }
-
- pw.print("Advancing standby heartbeat by ");
- pw.println(numBeats);
- synchronized (mLock) {
- advanceHeartbeatLocked(numBeats);
- }
- return 0;
- }
-
- void triggerDockState(boolean idleState) {
- final Intent dockIntent;
- if (idleState) {
- dockIntent = new Intent(Intent.ACTION_DOCK_IDLE);
- } else {
- dockIntent = new Intent(Intent.ACTION_DOCK_ACTIVE);
- }
- dockIntent.setPackage("android");
- dockIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL);
- }
-
- static void dumpHelp(PrintWriter pw) {
- pw.println("Job Scheduler (jobscheduler) dump options:");
- pw.println(" [-h] [package] ...");
- pw.println(" -h: print this help");
- pw.println(" [package] is an optional package name to limit the output to.");
- }
-
- /** Sort jobs by caller UID, then by Job ID. */
- private static void sortJobs(List<JobStatus> jobs) {
- Collections.sort(jobs, new Comparator<JobStatus>() {
- @Override
- public int compare(JobStatus o1, JobStatus o2) {
- int uid1 = o1.getUid();
- int uid2 = o2.getUid();
- int id1 = o1.getJobId();
- int id2 = o2.getJobId();
- if (uid1 != uid2) {
- return uid1 < uid2 ? -1 : 1;
- }
- return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
- }
- });
- }
-
- void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
- final int filterUidFinal = UserHandle.getAppId(filterUid);
- final long now = sSystemClock.millis();
- final long nowElapsed = sElapsedRealtimeClock.millis();
- final long nowUptime = sUptimeMillisClock.millis();
-
- final Predicate<JobStatus> predicate = (js) -> {
- return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
- || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
- };
- synchronized (mLock) {
- mConstants.dump(pw);
- for (StateController controller : mControllers) {
- pw.increaseIndent();
- controller.dumpConstants(pw);
- pw.decreaseIndent();
- }
- pw.println();
-
- pw.println(" Heartbeat:");
- pw.print(" Current: "); pw.println(mHeartbeat);
- pw.println(" Next");
- pw.print(" ACTIVE: "); pw.println(mNextBucketHeartbeat[0]);
- pw.print(" WORKING: "); pw.println(mNextBucketHeartbeat[1]);
- pw.print(" FREQUENT: "); pw.println(mNextBucketHeartbeat[2]);
- pw.print(" RARE: "); pw.println(mNextBucketHeartbeat[3]);
- pw.print(" Last heartbeat: ");
- TimeUtils.formatDuration(mLastHeartbeatTime, nowElapsed, pw);
- pw.println();
- pw.print(" Next heartbeat: ");
- TimeUtils.formatDuration(mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME,
- nowElapsed, pw);
- pw.println();
- pw.print(" In parole?: ");
- pw.print(mInParole);
- pw.println();
- pw.print(" In thermal throttling?: ");
- pw.print(mThermalConstraint);
- pw.println();
- pw.println();
-
- pw.println("Started users: " + Arrays.toString(mStartedUsers));
- pw.print("Registered ");
- pw.print(mJobs.size());
- pw.println(" jobs:");
- if (mJobs.size() > 0) {
- final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
- sortJobs(jobs);
- for (JobStatus job : jobs) {
- pw.print(" JOB #"); job.printUniqueId(pw); pw.print(": ");
- pw.println(job.toShortStringExceptUniqueId());
-
- // Skip printing details if the caller requested a filter
- if (!predicate.test(job)) {
- continue;
- }
-
- job.dump(pw, " ", true, nowElapsed);
- pw.print(" Last run heartbeat: ");
- pw.print(heartbeatWhenJobsLastRun(job));
- pw.println();
-
- pw.print(" Ready: ");
- pw.print(isReadyToBeExecutedLocked(job));
- pw.print(" (job=");
- pw.print(job.isReady());
- pw.print(" user=");
- pw.print(areUsersStartedLocked(job));
- pw.print(" !pending=");
- pw.print(!mPendingJobs.contains(job));
- pw.print(" !active=");
- pw.print(!isCurrentlyActiveLocked(job));
- pw.print(" !backingup=");
- pw.print(!(mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0));
- pw.print(" comp=");
- boolean componentPresent = false;
- try {
- componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
- job.getServiceComponent(),
- PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- job.getUserId()) != null);
- } catch (RemoteException e) {
- }
- pw.print(componentPresent);
- pw.println(")");
- }
- } else {
- pw.println(" None.");
- }
- for (int i=0; i<mControllers.size(); i++) {
- pw.println();
- pw.println(mControllers.get(i).getClass().getSimpleName() + ":");
- pw.increaseIndent();
- mControllers.get(i).dumpControllerStateLocked(pw, predicate);
- pw.decreaseIndent();
- }
- pw.println();
- pw.println("Uid priority overrides:");
- for (int i=0; i< mUidPriorityOverride.size(); i++) {
- int uid = mUidPriorityOverride.keyAt(i);
- if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
- pw.print(" "); pw.print(UserHandle.formatUid(uid));
- pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
- }
- }
- if (mBackingUpUids.size() > 0) {
- pw.println();
- pw.println("Backing up uids:");
- boolean first = true;
- for (int i = 0; i < mBackingUpUids.size(); i++) {
- int uid = mBackingUpUids.keyAt(i);
- if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
- if (first) {
- pw.print(" ");
- first = false;
- } else {
- pw.print(", ");
- }
- pw.print(UserHandle.formatUid(uid));
- }
- }
- pw.println();
- }
- pw.println();
- mJobPackageTracker.dump(pw, "", filterUidFinal);
- pw.println();
- if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
- pw.println();
- }
- pw.println("Pending queue:");
- for (int i=0; i<mPendingJobs.size(); i++) {
- JobStatus job = mPendingJobs.get(i);
- pw.print(" Pending #"); pw.print(i); pw.print(": ");
- pw.println(job.toShortString());
- job.dump(pw, " ", false, nowElapsed);
- int priority = evaluateJobPriorityLocked(job);
- pw.print(" Evaluated priority: ");
- pw.println(JobInfo.getPriorityString(priority));
-
- pw.print(" Tag: "); pw.println(job.getTag());
- pw.print(" Enq: ");
- TimeUtils.formatDuration(job.madePending - nowUptime, pw);
- pw.println();
- }
- pw.println();
- pw.println("Active jobs:");
- for (int i=0; i<mActiveServices.size(); i++) {
- JobServiceContext jsc = mActiveServices.get(i);
- pw.print(" Slot #"); pw.print(i); pw.print(": ");
- final JobStatus job = jsc.getRunningJobLocked();
- if (job == null) {
- if (jsc.mStoppedReason != null) {
- pw.print("inactive since ");
- TimeUtils.formatDuration(jsc.mStoppedTime, nowElapsed, pw);
- pw.print(", stopped because: ");
- pw.println(jsc.mStoppedReason);
- } else {
- pw.println("inactive");
- }
- continue;
- } else {
- pw.println(job.toShortString());
- pw.print(" Running for: ");
- TimeUtils.formatDuration(nowElapsed - jsc.getExecutionStartTimeElapsed(), pw);
- pw.print(", timeout at: ");
- TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
- pw.println();
- job.dump(pw, " ", false, nowElapsed);
- int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
- pw.print(" Evaluated priority: ");
- pw.println(JobInfo.getPriorityString(priority));
-
- pw.print(" Active at ");
- TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
- pw.print(", pending for ");
- TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
- pw.println();
- }
- }
- if (filterUid == -1) {
- pw.println();
- pw.print("mReadyToRock="); pw.println(mReadyToRock);
- pw.print("mReportedActive="); pw.println(mReportedActive);
- }
- pw.println();
-
- mConcurrencyManager.dumpLocked(pw, now, nowElapsed);
-
- pw.println();
- pw.print("PersistStats: ");
- pw.println(mJobs.getPersistStats());
- }
- pw.println();
- }
-
- void dumpInternalProto(final FileDescriptor fd, int filterUid) {
- ProtoOutputStream proto = new ProtoOutputStream(fd);
- final int filterUidFinal = UserHandle.getAppId(filterUid);
- final long now = sSystemClock.millis();
- final long nowElapsed = sElapsedRealtimeClock.millis();
- final long nowUptime = sUptimeMillisClock.millis();
- final Predicate<JobStatus> predicate = (js) -> {
- return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
- || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
- };
-
- synchronized (mLock) {
- final long settingsToken = proto.start(JobSchedulerServiceDumpProto.SETTINGS);
- mConstants.dump(proto);
- for (StateController controller : mControllers) {
- controller.dumpConstants(proto);
- }
- proto.end(settingsToken);
-
- proto.write(JobSchedulerServiceDumpProto.CURRENT_HEARTBEAT, mHeartbeat);
- proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[0]);
- proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[1]);
- proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[2]);
- proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[3]);
- proto.write(JobSchedulerServiceDumpProto.LAST_HEARTBEAT_TIME_MILLIS,
- mLastHeartbeatTime - nowUptime);
- proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT_TIME_MILLIS,
- mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME - nowUptime);
- proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
- proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint);
-
- for (int u : mStartedUsers) {
- proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
- }
- if (mJobs.size() > 0) {
- final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
- sortJobs(jobs);
- for (JobStatus job : jobs) {
- final long rjToken = proto.start(JobSchedulerServiceDumpProto.REGISTERED_JOBS);
- job.writeToShortProto(proto, JobSchedulerServiceDumpProto.RegisteredJob.INFO);
-
- // Skip printing details if the caller requested a filter
- if (!predicate.test(job)) {
- continue;
- }
-
- job.dump(proto, JobSchedulerServiceDumpProto.RegisteredJob.DUMP, true, nowElapsed);
-
- // isReadyToBeExecuted
- proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY,
- job.isReady());
- proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_USER_STARTED,
- areUsersStartedLocked(job));
- proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
- mPendingJobs.contains(job));
- proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
- isCurrentlyActiveLocked(job));
- proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_UID_BACKING_UP,
- mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0);
- boolean componentPresent = false;
- try {
- componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
- job.getServiceComponent(),
- PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- job.getUserId()) != null);
- } catch (RemoteException e) {
- }
- proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_PRESENT,
- componentPresent);
- proto.write(RegisteredJob.LAST_RUN_HEARTBEAT, heartbeatWhenJobsLastRun(job));
-
- proto.end(rjToken);
- }
- }
- for (StateController controller : mControllers) {
- controller.dumpControllerStateLocked(
- proto, JobSchedulerServiceDumpProto.CONTROLLERS, predicate);
- }
- for (int i=0; i< mUidPriorityOverride.size(); i++) {
- int uid = mUidPriorityOverride.keyAt(i);
- if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
- long pToken = proto.start(JobSchedulerServiceDumpProto.PRIORITY_OVERRIDES);
- proto.write(JobSchedulerServiceDumpProto.PriorityOverride.UID, uid);
- proto.write(JobSchedulerServiceDumpProto.PriorityOverride.OVERRIDE_VALUE,
- mUidPriorityOverride.valueAt(i));
- proto.end(pToken);
- }
- }
- for (int i = 0; i < mBackingUpUids.size(); i++) {
- int uid = mBackingUpUids.keyAt(i);
- if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
- proto.write(JobSchedulerServiceDumpProto.BACKING_UP_UIDS, uid);
- }
- }
-
- mJobPackageTracker.dump(proto, JobSchedulerServiceDumpProto.PACKAGE_TRACKER,
- filterUidFinal);
- mJobPackageTracker.dumpHistory(proto, JobSchedulerServiceDumpProto.HISTORY,
- filterUidFinal);
-
- for (JobStatus job : mPendingJobs) {
- final long pjToken = proto.start(JobSchedulerServiceDumpProto.PENDING_JOBS);
-
- job.writeToShortProto(proto, PendingJob.INFO);
- job.dump(proto, PendingJob.DUMP, false, nowElapsed);
- proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobPriorityLocked(job));
- proto.write(PendingJob.ENQUEUED_DURATION_MS, nowUptime - job.madePending);
-
- proto.end(pjToken);
- }
- for (JobServiceContext jsc : mActiveServices) {
- final long ajToken = proto.start(JobSchedulerServiceDumpProto.ACTIVE_JOBS);
- final JobStatus job = jsc.getRunningJobLocked();
-
- if (job == null) {
- final long ijToken = proto.start(ActiveJob.INACTIVE);
-
- proto.write(ActiveJob.InactiveJob.TIME_SINCE_STOPPED_MS,
- nowElapsed - jsc.mStoppedTime);
- if (jsc.mStoppedReason != null) {
- proto.write(ActiveJob.InactiveJob.STOPPED_REASON,
- jsc.mStoppedReason);
- }
-
- proto.end(ijToken);
- } else {
- final long rjToken = proto.start(ActiveJob.RUNNING);
-
- job.writeToShortProto(proto, ActiveJob.RunningJob.INFO);
-
- proto.write(ActiveJob.RunningJob.RUNNING_DURATION_MS,
- nowElapsed - jsc.getExecutionStartTimeElapsed());
- proto.write(ActiveJob.RunningJob.TIME_UNTIL_TIMEOUT_MS,
- jsc.getTimeoutElapsed() - nowElapsed);
-
- job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
-
- proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
- evaluateJobPriorityLocked(jsc.getRunningJobLocked()));
-
- proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
- nowUptime - job.madeActive);
- proto.write(ActiveJob.RunningJob.PENDING_DURATION_MS,
- job.madeActive - job.madePending);
-
- proto.end(rjToken);
- }
- proto.end(ajToken);
- }
- if (filterUid == -1) {
- proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
- proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);
- }
- mConcurrencyManager.dumpProtoLocked(proto,
- JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed);
- }
-
- proto.flush();
- }
-}
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
deleted file mode 100644
index e361441..0000000
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.ShellCommand;
-import android.os.UserHandle;
-
-import java.io.PrintWriter;
-
-public final class JobSchedulerShellCommand extends ShellCommand {
- public static final int CMD_ERR_NO_PACKAGE = -1000;
- public static final int CMD_ERR_NO_JOB = -1001;
- public static final int CMD_ERR_CONSTRAINTS = -1002;
-
- JobSchedulerService mInternal;
- IPackageManager mPM;
-
- JobSchedulerShellCommand(JobSchedulerService service) {
- mInternal = service;
- mPM = AppGlobals.getPackageManager();
- }
-
- @Override
- public int onCommand(String cmd) {
- final PrintWriter pw = getOutPrintWriter();
- try {
- switch (cmd != null ? cmd : "") {
- case "run":
- return runJob(pw);
- case "timeout":
- return timeout(pw);
- case "cancel":
- return cancelJob(pw);
- case "monitor-battery":
- return monitorBattery(pw);
- case "get-battery-seq":
- return getBatterySeq(pw);
- case "get-battery-charging":
- return getBatteryCharging(pw);
- case "get-battery-not-low":
- return getBatteryNotLow(pw);
- case "get-storage-seq":
- return getStorageSeq(pw);
- case "get-storage-not-low":
- return getStorageNotLow(pw);
- case "get-job-state":
- return getJobState(pw);
- case "heartbeat":
- return doHeartbeat(pw);
- case "trigger-dock-state":
- return triggerDockState(pw);
- default:
- return handleDefaultCommands(cmd);
- }
- } catch (Exception e) {
- pw.println("Exception: " + e);
- }
- return -1;
- }
-
- private void checkPermission(String operation) throws Exception {
- final int uid = Binder.getCallingUid();
- if (uid == 0) {
- // Root can do anything.
- return;
- }
- final int perm = mPM.checkUidPermission(
- "android.permission.CHANGE_APP_IDLE_STATE", uid);
- if (perm != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Uid " + uid
- + " not permitted to " + operation);
- }
- }
-
- private boolean printError(int errCode, String pkgName, int userId, int jobId) {
- PrintWriter pw;
- switch (errCode) {
- case CMD_ERR_NO_PACKAGE:
- pw = getErrPrintWriter();
- pw.print("Package not found: ");
- pw.print(pkgName);
- pw.print(" / user ");
- pw.println(userId);
- return true;
-
- case CMD_ERR_NO_JOB:
- pw = getErrPrintWriter();
- pw.print("Could not find job ");
- pw.print(jobId);
- pw.print(" in package ");
- pw.print(pkgName);
- pw.print(" / user ");
- pw.println(userId);
- return true;
-
- case CMD_ERR_CONSTRAINTS:
- pw = getErrPrintWriter();
- pw.print("Job ");
- pw.print(jobId);
- pw.print(" in package ");
- pw.print(pkgName);
- pw.print(" / user ");
- pw.print(userId);
- pw.println(" has functional constraints but --force not specified");
- return true;
-
- default:
- return false;
- }
- }
-
- private int runJob(PrintWriter pw) throws Exception {
- checkPermission("force scheduled jobs");
-
- boolean force = false;
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-f":
- case "--force":
- force = true;
- break;
-
- case "-u":
- case "--user":
- userId = Integer.parseInt(getNextArgRequired());
- break;
-
- default:
- pw.println("Error: unknown option '" + opt + "'");
- return -1;
- }
- }
-
- final String pkgName = getNextArgRequired();
- final int jobId = Integer.parseInt(getNextArgRequired());
-
- final long ident = Binder.clearCallingIdentity();
- try {
- int ret = mInternal.executeRunCommand(pkgName, userId, jobId, force);
- if (printError(ret, pkgName, userId, jobId)) {
- return ret;
- }
-
- // success!
- pw.print("Running job");
- if (force) {
- pw.print(" [FORCED]");
- }
- pw.println();
-
- return ret;
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private int timeout(PrintWriter pw) throws Exception {
- checkPermission("force timeout jobs");
-
- int userId = UserHandle.USER_ALL;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-u":
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
-
- default:
- pw.println("Error: unknown option '" + opt + "'");
- return -1;
- }
- }
-
- if (userId == UserHandle.USER_CURRENT) {
- userId = ActivityManager.getCurrentUser();
- }
-
- final String pkgName = getNextArg();
- final String jobIdStr = getNextArg();
- final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
-
- final long ident = Binder.clearCallingIdentity();
- try {
- return mInternal.executeTimeoutCommand(pw, pkgName, userId, jobIdStr != null, jobId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private int cancelJob(PrintWriter pw) throws Exception {
- checkPermission("cancel jobs");
-
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-u":
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
-
- default:
- pw.println("Error: unknown option '" + opt + "'");
- return -1;
- }
- }
-
- if (userId < 0) {
- pw.println("Error: must specify a concrete user ID");
- return -1;
- }
-
- final String pkgName = getNextArg();
- final String jobIdStr = getNextArg();
- final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
-
- final long ident = Binder.clearCallingIdentity();
- try {
- return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private int monitorBattery(PrintWriter pw) throws Exception {
- checkPermission("change battery monitoring");
- String opt = getNextArgRequired();
- boolean enabled;
- if ("on".equals(opt)) {
- enabled = true;
- } else if ("off".equals(opt)) {
- enabled = false;
- } else {
- getErrPrintWriter().println("Error: unknown option " + opt);
- return 1;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- mInternal.setMonitorBattery(enabled);
- if (enabled) pw.println("Battery monitoring enabled");
- else pw.println("Battery monitoring disabled");
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return 0;
- }
-
- private int getBatterySeq(PrintWriter pw) {
- int seq = mInternal.getBatterySeq();
- pw.println(seq);
- return 0;
- }
-
- private int getBatteryCharging(PrintWriter pw) {
- boolean val = mInternal.getBatteryCharging();
- pw.println(val);
- return 0;
- }
-
- private int getBatteryNotLow(PrintWriter pw) {
- boolean val = mInternal.getBatteryNotLow();
- pw.println(val);
- return 0;
- }
-
- private int getStorageSeq(PrintWriter pw) {
- int seq = mInternal.getStorageSeq();
- pw.println(seq);
- return 0;
- }
-
- private int getStorageNotLow(PrintWriter pw) {
- boolean val = mInternal.getStorageNotLow();
- pw.println(val);
- return 0;
- }
-
- private int getJobState(PrintWriter pw) throws Exception {
- checkPermission("force timeout jobs");
-
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-u":
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
-
- default:
- pw.println("Error: unknown option '" + opt + "'");
- return -1;
- }
- }
-
- if (userId == UserHandle.USER_CURRENT) {
- userId = ActivityManager.getCurrentUser();
- }
-
- final String pkgName = getNextArgRequired();
- final String jobIdStr = getNextArgRequired();
- final int jobId = Integer.parseInt(jobIdStr);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- int ret = mInternal.getJobState(pw, pkgName, userId, jobId);
- printError(ret, pkgName, userId, jobId);
- return ret;
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private int doHeartbeat(PrintWriter pw) throws Exception {
- checkPermission("manipulate scheduler heartbeat");
-
- final String arg = getNextArg();
- final int numBeats = (arg != null) ? Integer.parseInt(arg) : 0;
-
- final long ident = Binder.clearCallingIdentity();
- try {
- return mInternal.executeHeartbeatCommand(pw, numBeats);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private int triggerDockState(PrintWriter pw) throws Exception {
- checkPermission("trigger wireless charging dock state");
-
- final String opt = getNextArgRequired();
- boolean idleState;
- if ("idle".equals(opt)) {
- idleState = true;
- } else if ("active".equals(opt)) {
- idleState = false;
- } else {
- getErrPrintWriter().println("Error: unknown option " + opt);
- return 1;
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- mInternal.triggerDockState(idleState);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return 0;
- }
-
- @Override
- public void onHelp() {
- final PrintWriter pw = getOutPrintWriter();
-
- pw.println("Job scheduler (jobscheduler) commands:");
- pw.println(" help");
- pw.println(" Print this help text.");
- pw.println(" run [-f | --force] [-u | --user USER_ID] PACKAGE JOB_ID");
- pw.println(" Trigger immediate execution of a specific scheduled job.");
- pw.println(" Options:");
- pw.println(" -f or --force: run the job even if technical constraints such as");
- pw.println(" connectivity are not currently met");
- pw.println(" -u or --user: specify which user's job is to be run; the default is");
- pw.println(" the primary or system user");
- pw.println(" timeout [-u | --user USER_ID] [PACKAGE] [JOB_ID]");
- pw.println(" Trigger immediate timeout of currently executing jobs, as if their.");
- pw.println(" execution timeout had expired.");
- pw.println(" Options:");
- pw.println(" -u or --user: specify which user's job is to be run; the default is");
- pw.println(" all users");
- pw.println(" cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
- pw.println(" Cancel a scheduled job. If a job ID is not supplied, all jobs scheduled");
- pw.println(" by that package will be canceled. USE WITH CAUTION.");
- pw.println(" Options:");
- pw.println(" -u or --user: specify which user's job is to be run; the default is");
- pw.println(" the primary or system user");
- pw.println(" heartbeat [num]");
- pw.println(" With no argument, prints the current standby heartbeat. With a positive");
- pw.println(" argument, advances the standby heartbeat by that number.");
- pw.println(" monitor-battery [on|off]");
- pw.println(" Control monitoring of all battery changes. Off by default. Turning");
- pw.println(" on makes get-battery-seq useful.");
- pw.println(" get-battery-seq");
- pw.println(" Return the last battery update sequence number that was received.");
- pw.println(" get-battery-charging");
- pw.println(" Return whether the battery is currently considered to be charging.");
- pw.println(" get-battery-not-low");
- pw.println(" Return whether the battery is currently considered to not be low.");
- pw.println(" get-storage-seq");
- pw.println(" Return the last storage update sequence number that was received.");
- pw.println(" get-storage-not-low");
- pw.println(" Return whether storage is currently considered to not be low.");
- pw.println(" get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
- pw.println(" Return the current state of a job, may be any combination of:");
- pw.println(" pending: currently on the pending list, waiting to be active");
- pw.println(" active: job is actively running");
- pw.println(" user-stopped: job can't run because its user is stopped");
- pw.println(" backing-up: job can't run because app is currently backing up its data");
- pw.println(" no-component: job can't run because its component is not available");
- pw.println(" ready: job is ready to run (all constraints satisfied or bypassed)");
- pw.println(" waiting: if nothing else above is printed, job not ready to run");
- pw.println(" Options:");
- pw.println(" -u or --user: specify which user's job is to be run; the default is");
- pw.println(" the primary or system user");
- pw.println(" trigger-dock-state [idle|active]");
- pw.println(" Trigger wireless charging dock state. Active by default.");
- pw.println();
- }
-
-}
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
deleted file mode 100644
index 7da128f..0000000
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ /dev/null
@@ -1,856 +0,0 @@
-/*
- * 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;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.app.ActivityManager;
-import android.app.job.IJobCallback;
-import android.app.job.IJobService;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobWorkItem;
-import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-import com.android.server.job.controllers.JobStatus;
-
-/**
- * Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
- * class.
- *
- * There are two important interactions into this class from the
- * {@link com.android.server.job.JobSchedulerService}. To execute a job and to cancel a job.
- * - Execution of a new job is handled by the {@link #mAvailable}. This bit is flipped once when a
- * job lands, and again when it is complete.
- * - Cancelling is trickier, because there are also interactions from the client. It's possible
- * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
- * {@link #doCancelLocked} after the client has already finished. This is handled by having
- * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
- * the context is still valid.
- * To mitigate this, we avoid sending duplicate onStopJob()
- * calls to the client after they've specified jobFinished().
- */
-public final class JobServiceContext implements ServiceConnection {
- private static final boolean DEBUG = JobSchedulerService.DEBUG;
- private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
-
- private static final String TAG = "JobServiceContext";
- /** Amount of time a job is allowed to execute for before being considered timed-out. */
- public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
- /** Amount of time the JobScheduler waits for the initial service launch+bind. */
- private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
- /** Amount of time the JobScheduler will wait for a response from an app for a message. */
- private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
-
- private static final String[] VERB_STRINGS = {
- "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
- };
-
- // States that a job occupies while interacting with the client.
- static final int VERB_BINDING = 0;
- static final int VERB_STARTING = 1;
- static final int VERB_EXECUTING = 2;
- static final int VERB_STOPPING = 3;
- static final int VERB_FINISHED = 4;
-
- // Messages that result from interactions with the client service.
- /** System timed out waiting for a response. */
- private static final int MSG_TIMEOUT = 0;
-
- public static final int NO_PREFERRED_UID = -1;
-
- private final Handler mCallbackHandler;
- /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
- private final JobCompletedListener mCompletedListener;
- /** Used for service binding, etc. */
- private final Context mContext;
- private final Object mLock;
- private final IBatteryStats mBatteryStats;
- private final JobPackageTracker mJobPackageTracker;
- private PowerManager.WakeLock mWakeLock;
-
- // Execution state.
- private JobParameters mParams;
- @VisibleForTesting
- int mVerb;
- private boolean mCancelled;
-
- /**
- * All the information maintained about the job currently being executed.
- *
- * Any reads (dereferences) not done from the handler thread must be synchronized on
- * {@link #mLock}.
- * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
- */
- private JobStatus mRunningJob;
- private JobCallback mRunningCallback;
- /** Used to store next job to run when current job is to be preempted. */
- private int mPreferredUid;
- IJobService service;
-
- /**
- * Whether this context is free. This is set to false at the start of execution, and reset to
- * true when execution is complete.
- */
- @GuardedBy("mLock")
- private boolean mAvailable;
- /** Track start time. */
- private long mExecutionStartTimeElapsed;
- /** Track when job will timeout. */
- private long mTimeoutElapsed;
-
- // Debugging: reason this job was last stopped.
- public String mStoppedReason;
-
- // Debugging: time this job was last stopped.
- public long mStoppedTime;
-
- final class JobCallback extends IJobCallback.Stub {
- public String mStoppedReason;
- public long mStoppedTime;
-
- @Override
- public void acknowledgeStartMessage(int jobId, boolean ongoing) {
- doAcknowledgeStartMessage(this, jobId, ongoing);
- }
-
- @Override
- public void acknowledgeStopMessage(int jobId, boolean reschedule) {
- doAcknowledgeStopMessage(this, jobId, reschedule);
- }
-
- @Override
- public JobWorkItem dequeueWork(int jobId) {
- return doDequeueWork(this, jobId);
- }
-
- @Override
- public boolean completeWork(int jobId, int workId) {
- return doCompleteWork(this, jobId, workId);
- }
-
- @Override
- public void jobFinished(int jobId, boolean reschedule) {
- doJobFinished(this, jobId, reschedule);
- }
- }
-
- JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
- JobPackageTracker tracker, Looper looper) {
- this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
- }
-
- @VisibleForTesting
- JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
- JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
- mContext = context;
- mLock = lock;
- mBatteryStats = batteryStats;
- mJobPackageTracker = tracker;
- mCallbackHandler = new JobServiceHandler(looper);
- mCompletedListener = completedListener;
- mAvailable = true;
- mVerb = VERB_FINISHED;
- mPreferredUid = NO_PREFERRED_UID;
- }
-
- /**
- * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()}
- * and ensure it is null to make sure this is a valid context.
- * @param job The status of the job that we are going to run.
- * @return True if the job is valid and is running. False if the job cannot be executed.
- */
- boolean executeRunnableJob(JobStatus job) {
- synchronized (mLock) {
- if (!mAvailable) {
- Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
- return false;
- }
-
- mPreferredUid = NO_PREFERRED_UID;
-
- mRunningJob = job;
- mRunningCallback = new JobCallback();
- final boolean isDeadlineExpired =
- job.hasDeadlineConstraint() &&
- (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
- Uri[] triggeredUris = null;
- if (job.changedUris != null) {
- triggeredUris = new Uri[job.changedUris.size()];
- job.changedUris.toArray(triggeredUris);
- }
- String[] triggeredAuthorities = null;
- if (job.changedAuthorities != null) {
- triggeredAuthorities = new String[job.changedAuthorities.size()];
- job.changedAuthorities.toArray(triggeredAuthorities);
- }
- final JobInfo ji = job.getJob();
- mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
- ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
- isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
- mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
-
- final long whenDeferred = job.getWhenStandbyDeferred();
- if (whenDeferred > 0) {
- final long deferral = mExecutionStartTimeElapsed - whenDeferred;
- EventLog.writeEvent(EventLogTags.JOB_DEFERRED_EXECUTION, deferral);
- if (DEBUG_STANDBY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Starting job deferred for standby by ");
- TimeUtils.formatDuration(deferral, sb);
- sb.append(" ms : ");
- sb.append(job.toShortString());
- Slog.v(TAG, sb.toString());
- }
- }
-
- // Once we'e begun executing a job, we by definition no longer care whether
- // it was inflated from disk with not-yet-coherent delay/deadline bounds.
- job.clearPersistedUtcTimes();
-
- mVerb = VERB_BINDING;
- scheduleOpTimeOutLocked();
- final Intent intent = new Intent().setComponent(job.getServiceComponent());
- boolean binding = false;
- try {
- binding = mContext.bindServiceAsUser(intent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_NOT_PERCEPTIBLE,
- new UserHandle(job.getUserId()));
- } catch (SecurityException e) {
- // Some permission policy, for example INTERACT_ACROSS_USERS and
- // android:singleUser, can result in a SecurityException being thrown from
- // bindServiceAsUser(). If this happens, catch it and fail gracefully.
- Slog.w(TAG, "Job service " + job.getServiceComponent().getShortClassName()
- + " cannot be executed: " + e.getMessage());
- binding = false;
- }
- if (!binding) {
- if (DEBUG) {
- Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
- }
- mRunningJob = null;
- mRunningCallback = null;
- mParams = null;
- mExecutionStartTimeElapsed = 0L;
- mVerb = VERB_FINISHED;
- removeOpTimeOutLocked();
- return false;
- }
- mJobPackageTracker.noteActive(job);
- try {
- mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid(),
- job.getStandbyBucket(), job.getJobId());
- } catch (RemoteException e) {
- // Whatever.
- }
- final String jobPackage = job.getSourcePackageName();
- final int jobUserId = job.getSourceUserId();
- UsageStatsManagerInternal usageStats =
- LocalServices.getService(UsageStatsManagerInternal.class);
- usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
- JobSchedulerInternal jobScheduler =
- LocalServices.getService(JobSchedulerInternal.class);
- jobScheduler.noteJobStart(jobPackage, jobUserId);
- mAvailable = false;
- mStoppedReason = null;
- mStoppedTime = 0;
- return true;
- }
- }
-
- /**
- * Used externally to query the running job. Will return null if there is no job running.
- */
- JobStatus getRunningJobLocked() {
- return mRunningJob;
- }
-
- /**
- * Used only for debugging. Will return <code>"<null>"</code> if there is no job running.
- */
- private String getRunningJobNameLocked() {
- return mRunningJob != null ? mRunningJob.toShortString() : "<null>";
- }
-
- /** Called externally when a job that was scheduled for execution should be cancelled. */
- @GuardedBy("mLock")
- void cancelExecutingJobLocked(int reason, String debugReason) {
- doCancelLocked(reason, debugReason);
- }
-
- @GuardedBy("mLock")
- void preemptExecutingJobLocked() {
- doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption");
- }
-
- int getPreferredUid() {
- return mPreferredUid;
- }
-
- void clearPreferredUid() {
- mPreferredUid = NO_PREFERRED_UID;
- }
-
- long getExecutionStartTimeElapsed() {
- return mExecutionStartTimeElapsed;
- }
-
- long getTimeoutElapsed() {
- return mTimeoutElapsed;
- }
-
- @GuardedBy("mLock")
- boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
- String reason) {
- final JobStatus executing = getRunningJobLocked();
- if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId())
- && (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
- && (!matchJobId || jobId == executing.getJobId())) {
- if (mVerb == VERB_EXECUTING) {
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
- sendStopMessageLocked("force timeout from shell");
- return true;
- }
- }
- return false;
- }
-
- void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
- doCallback(cb, reschedule, "app called jobFinished");
- }
-
- void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
- doCallback(cb, reschedule, null);
- }
-
- void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
- doCallback(cb, ongoing, "finished start");
- }
-
- JobWorkItem doDequeueWork(JobCallback cb, int jobId) {
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- assertCallerLocked(cb);
- if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
- // This job is either all done, or on its way out. Either way, it
- // should not dispatch any more work. We will pick up any remaining
- // work the next time we start the job again.
- return null;
- }
- final JobWorkItem work = mRunningJob.dequeueWorkLocked();
- if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
- // This will finish the job.
- doCallbackLocked(false, "last work dequeued");
- }
- return work;
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- boolean doCompleteWork(JobCallback cb, int jobId, int workId) {
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- assertCallerLocked(cb);
- return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
- * we intend to send to the client - we stop sending work when the service is unbound so until
- * then we keep the wakelock.
- * @param name The concrete component name of the service that has been connected.
- * @param service The IBinder of the Service's communication channel,
- */
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- JobStatus runningJob;
- synchronized (mLock) {
- // This isn't strictly necessary b/c the JobServiceHandler is running on the main
- // looper and at this point we can't get any binder callbacks from the client. Better
- // safe than sorry.
- runningJob = mRunningJob;
-
- if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
- closeAndCleanupJobLocked(true /* needsReschedule */,
- "connected for different component");
- return;
- }
- this.service = IJobService.Stub.asInterface(service);
- final PowerManager pm =
- (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- runningJob.getTag());
- wl.setWorkSource(deriveWorkSource(runningJob));
- wl.setReferenceCounted(false);
- wl.acquire();
-
- // We use a new wakelock instance per job. In rare cases there is a race between
- // teardown following job completion/cancellation and new job service spin-up
- // such that if we simply assign mWakeLock to be the new instance, we orphan
- // the currently-live lock instead of cleanly replacing it. Watch for this and
- // explicitly fast-forward the release if we're in that situation.
- if (mWakeLock != null) {
- Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock
- + " tag=" + mWakeLock.getTag());
- mWakeLock.release();
- }
- mWakeLock = wl;
- doServiceBoundLocked();
- }
- }
-
- private WorkSource deriveWorkSource(JobStatus runningJob) {
- final int jobUid = runningJob.getSourceUid();
- if (WorkSource.isChainedBatteryAttributionEnabled(mContext)) {
- WorkSource workSource = new WorkSource();
- workSource.createWorkChain()
- .addNode(jobUid, null)
- .addNode(android.os.Process.SYSTEM_UID, "JobScheduler");
- return workSource;
- } else {
- return new WorkSource(jobUid);
- }
- }
-
- /** If the client service crashes we reschedule this job and clean up. */
- @Override
- public void onServiceDisconnected(ComponentName name) {
- synchronized (mLock) {
- closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
- }
- }
-
- /**
- * This class is reused across different clients, and passes itself in as a callback. Check
- * whether the client exercising the callback is the client we expect.
- * @return True if the binder calling is coming from the client we expect.
- */
- private boolean verifyCallerLocked(JobCallback cb) {
- if (mRunningCallback != cb) {
- if (DEBUG) {
- Slog.d(TAG, "Stale callback received, ignoring.");
- }
- return false;
- }
- return true;
- }
-
- private void assertCallerLocked(JobCallback cb) {
- if (!verifyCallerLocked(cb)) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Caller no longer running");
- if (cb.mStoppedReason != null) {
- sb.append(", last stopped ");
- TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
- sb.append(" because: ");
- sb.append(cb.mStoppedReason);
- }
- throw new SecurityException(sb.toString());
- }
- }
-
- /**
- * Scheduling of async messages (basically timeouts at this point).
- */
- private class JobServiceHandler extends Handler {
- JobServiceHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_TIMEOUT:
- synchronized (mLock) {
- if (message.obj == mRunningCallback) {
- handleOpTimeoutLocked();
- } else {
- JobCallback jc = (JobCallback)message.obj;
- StringBuilder sb = new StringBuilder(128);
- sb.append("Ignoring timeout of no longer active job");
- if (jc.mStoppedReason != null) {
- sb.append(", stopped ");
- TimeUtils.formatDuration(sElapsedRealtimeClock.millis()
- - jc.mStoppedTime, sb);
- sb.append(" because: ");
- sb.append(jc.mStoppedReason);
- }
- Slog.w(TAG, sb.toString());
- }
- }
- break;
- default:
- Slog.e(TAG, "Unrecognised message: " + message);
- }
- }
- }
-
- @GuardedBy("mLock")
- void doServiceBoundLocked() {
- removeOpTimeOutLocked();
- handleServiceBoundLocked();
- }
-
- void doCallback(JobCallback cb, boolean reschedule, String reason) {
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- if (!verifyCallerLocked(cb)) {
- return;
- }
- doCallbackLocked(reschedule, reason);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @GuardedBy("mLock")
- void doCallbackLocked(boolean reschedule, String reason) {
- if (DEBUG) {
- Slog.d(TAG, "doCallback of : " + mRunningJob
- + " v:" + VERB_STRINGS[mVerb]);
- }
- removeOpTimeOutLocked();
-
- if (mVerb == VERB_STARTING) {
- handleStartedLocked(reschedule);
- } else if (mVerb == VERB_EXECUTING ||
- mVerb == VERB_STOPPING) {
- handleFinishedLocked(reschedule, reason);
- } else {
- if (DEBUG) {
- Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
- }
- }
- }
-
- @GuardedBy("mLock")
- void doCancelLocked(int arg1, String debugReason) {
- if (mVerb == VERB_FINISHED) {
- if (DEBUG) {
- Slog.d(TAG,
- "Trying to process cancel for torn-down context, ignoring.");
- }
- return;
- }
- mParams.setStopReason(arg1, debugReason);
- if (arg1 == JobParameters.REASON_PREEMPT) {
- mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
- NO_PREFERRED_UID;
- }
- handleCancelLocked(debugReason);
- }
-
- /** Start the job on the service. */
- @GuardedBy("mLock")
- private void handleServiceBoundLocked() {
- if (DEBUG) {
- Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked());
- }
- if (mVerb != VERB_BINDING) {
- Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
- + VERB_STRINGS[mVerb]);
- closeAndCleanupJobLocked(false /* reschedule */, "started job not pending");
- return;
- }
- if (mCancelled) {
- if (DEBUG) {
- Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
- + mRunningJob);
- }
- closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind");
- return;
- }
- try {
- mVerb = VERB_STARTING;
- scheduleOpTimeOutLocked();
- service.startJob(mParams);
- } catch (Exception e) {
- // We catch 'Exception' because client-app malice or bugs might induce a wide
- // range of possible exception-throw outcomes from startJob() and its handling
- // of the client's ParcelableBundle extras.
- Slog.e(TAG, "Error sending onStart message to '" +
- mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
- }
- }
-
- /**
- * State behaviours.
- * VERB_STARTING -> Successful start, change job to VERB_EXECUTING and post timeout.
- * _PENDING -> Error
- * _EXECUTING -> Error
- * _STOPPING -> Error
- */
- @GuardedBy("mLock")
- private void handleStartedLocked(boolean workOngoing) {
- switch (mVerb) {
- case VERB_STARTING:
- mVerb = VERB_EXECUTING;
- if (!workOngoing) {
- // Job is finished already so fast-forward to handleFinished.
- handleFinishedLocked(false, "onStartJob returned false");
- return;
- }
- if (mCancelled) {
- if (DEBUG) {
- Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
- }
- // Cancelled *while* waiting for acknowledgeStartMessage from client.
- handleCancelLocked(null);
- return;
- }
- scheduleOpTimeOutLocked();
- break;
- default:
- Slog.e(TAG, "Handling started job but job wasn't starting! Was "
- + VERB_STRINGS[mVerb] + ".");
- return;
- }
- }
-
- /**
- * VERB_EXECUTING -> Client called jobFinished(), clean up and notify done.
- * _STOPPING -> Successful finish, clean up and notify done.
- * _STARTING -> Error
- * _PENDING -> Error
- */
- @GuardedBy("mLock")
- private void handleFinishedLocked(boolean reschedule, String reason) {
- switch (mVerb) {
- case VERB_EXECUTING:
- case VERB_STOPPING:
- closeAndCleanupJobLocked(reschedule, reason);
- break;
- default:
- Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
- "executed. Was " + VERB_STRINGS[mVerb] + ".");
- }
- }
-
- /**
- * A job can be in various states when a cancel request comes in:
- * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
- * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
- * _STARTING -> Mark as cancelled and wait for
- * {@link JobServiceContext#doAcknowledgeStartMessage}
- * _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
- * in the message queue.
- * _ENDING -> No point in doing anything here, so we ignore.
- */
- @GuardedBy("mLock")
- private void handleCancelLocked(String reason) {
- if (JobSchedulerService.DEBUG) {
- Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
- + VERB_STRINGS[mVerb]);
- }
- switch (mVerb) {
- case VERB_BINDING:
- case VERB_STARTING:
- mCancelled = true;
- applyStoppedReasonLocked(reason);
- break;
- case VERB_EXECUTING:
- sendStopMessageLocked(reason);
- break;
- case VERB_STOPPING:
- // Nada.
- break;
- default:
- Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
- break;
- }
- }
-
- /** Process MSG_TIMEOUT here. */
- @GuardedBy("mLock")
- private void handleOpTimeoutLocked() {
- switch (mVerb) {
- case VERB_BINDING:
- Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked()
- + ", dropping.");
- closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
- break;
- case VERB_STARTING:
- // Client unresponsive - wedged or failed to respond in time. We don't really
- // know what happened so let's log it and notify the JobScheduler
- // FINISHED/NO-RETRY.
- Slog.w(TAG, "No response from client for onStartJob "
- + getRunningJobNameLocked());
- closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
- break;
- case VERB_STOPPING:
- // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
- Slog.w(TAG, "No response from client for onStopJob "
- + getRunningJobNameLocked());
- closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
- break;
- case VERB_EXECUTING:
- // Not an error - client ran out of time.
- Slog.i(TAG, "Client timed out while executing (no jobFinished received), " +
- "sending onStop: " + getRunningJobNameLocked());
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
- sendStopMessageLocked("timeout while executing");
- break;
- default:
- Slog.e(TAG, "Handling timeout for an invalid job state: "
- + getRunningJobNameLocked() + ", dropping.");
- closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");
- }
- }
-
- /**
- * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
- * VERB_STOPPING.
- */
- @GuardedBy("mLock")
- private void sendStopMessageLocked(String reason) {
- removeOpTimeOutLocked();
- if (mVerb != VERB_EXECUTING) {
- Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
- closeAndCleanupJobLocked(false /* reschedule */, reason);
- return;
- }
- try {
- applyStoppedReasonLocked(reason);
- mVerb = VERB_STOPPING;
- scheduleOpTimeOutLocked();
- service.stopJob(mParams);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error sending onStopJob to client.", e);
- // The job's host app apparently crashed during the job, so we should reschedule.
- closeAndCleanupJobLocked(true /* reschedule */, "host crashed when trying to stop");
- }
- }
-
- /**
- * The provided job has finished, either by calling
- * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
- * or from acknowledging the stop message we sent. Either way, we're done tracking it and
- * we want to clean up internally.
- */
- @GuardedBy("mLock")
- private void closeAndCleanupJobLocked(boolean reschedule, String reason) {
- final JobStatus completedJob;
- if (mVerb == VERB_FINISHED) {
- return;
- }
- applyStoppedReasonLocked(reason);
- completedJob = mRunningJob;
- mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
- try {
- mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
- mRunningJob.getSourceUid(), mParams.getStopReason(),
- mRunningJob.getStandbyBucket(), mRunningJob.getJobId());
- } catch (RemoteException e) {
- // Whatever.
- }
- if (mWakeLock != null) {
- mWakeLock.release();
- }
- mContext.unbindService(JobServiceContext.this);
- mWakeLock = null;
- mRunningJob = null;
- mRunningCallback = null;
- mParams = null;
- mVerb = VERB_FINISHED;
- mCancelled = false;
- service = null;
- mAvailable = true;
- removeOpTimeOutLocked();
- mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
- }
-
- private void applyStoppedReasonLocked(String reason) {
- if (reason != null && mStoppedReason == null) {
- mStoppedReason = reason;
- mStoppedTime = sElapsedRealtimeClock.millis();
- if (mRunningCallback != null) {
- mRunningCallback.mStoppedReason = mStoppedReason;
- mRunningCallback.mStoppedTime = mStoppedTime;
- }
- }
- }
-
- /**
- * Called when sending a message to the client, over whose execution we have no control. If
- * we haven't received a response in a certain amount of time, we want to give up and carry
- * on with life.
- */
- private void scheduleOpTimeOutLocked() {
- removeOpTimeOutLocked();
-
- final long timeoutMillis;
- switch (mVerb) {
- case VERB_EXECUTING:
- timeoutMillis = EXECUTING_TIMESLICE_MILLIS;
- break;
-
- case VERB_BINDING:
- timeoutMillis = OP_BIND_TIMEOUT_MILLIS;
- break;
-
- default:
- timeoutMillis = OP_TIMEOUT_MILLIS;
- break;
- }
- if (DEBUG) {
- Slog.d(TAG, "Scheduling time out for '" +
- mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
- mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
- }
- Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
- mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
- mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
- }
-
-
- private void removeOpTimeOutLocked() {
- mCallbackHandler.removeMessages(MSG_TIMEOUT);
- }
-}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
deleted file mode 100644
index d69faf3..0000000
--- a/services/core/java/com/android/server/job/JobStore.java
+++ /dev/null
@@ -1,1285 +0,0 @@
-/*
- * 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;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.JobSchedulerService.sSystemClock;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.app.job.JobInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.NetworkRequest;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.Xml;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.BitUtils;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.server.IoThread;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
-import com.android.server.job.controllers.JobStatus;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
- * reference, so none of the functions in this class should make a copy.
- * Also handles read/write of persisted jobs.
- *
- * Note on locking:
- * All callers to this class must <strong>lock on the class object they are calling</strong>.
- * This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable}
- * and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
- * object.
- *
- * Test:
- * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
- */
-public final class JobStore {
- private static final String TAG = "JobStore";
- private static final boolean DEBUG = JobSchedulerService.DEBUG;
-
- /** Threshold to adjust how often we want to write to the db. */
- private static final long JOB_PERSIST_DELAY = 2000L;
-
- final Object mLock;
- final Object mWriteScheduleLock; // used solely for invariants around write scheduling
- final JobSet mJobSet; // per-caller-uid and per-source-uid tracking
- final Context mContext;
-
- // Bookkeeping around incorrect boot-time system clock
- private final long mXmlTimestamp;
- private boolean mRtcGood;
-
- @GuardedBy("mWriteScheduleLock")
- private boolean mWriteScheduled;
-
- @GuardedBy("mWriteScheduleLock")
- private boolean mWriteInProgress;
-
- private static final Object sSingletonLock = new Object();
- private final AtomicFile mJobsFile;
- /** Handler backed by IoThread for writing to disk. */
- private final Handler mIoHandler = IoThread.getHandler();
- private static JobStore sSingleton;
-
- private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
-
- /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
- static JobStore initAndGet(JobSchedulerService jobManagerService) {
- synchronized (sSingletonLock) {
- if (sSingleton == null) {
- sSingleton = new JobStore(jobManagerService.getContext(),
- jobManagerService.getLock(), Environment.getDataDirectory());
- }
- return sSingleton;
- }
- }
-
- /**
- * @return A freshly initialized job store object, with no loaded jobs.
- */
- @VisibleForTesting
- public static JobStore initAndGetForTesting(Context context, File dataDir) {
- JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir);
- jobStoreUnderTest.clear();
- return jobStoreUnderTest;
- }
-
- /**
- * Construct the instance of the job store. This results in a blocking read from disk.
- */
- private JobStore(Context context, Object lock, File dataDir) {
- mLock = lock;
- mWriteScheduleLock = new Object();
- mContext = context;
-
- File systemDir = new File(dataDir, "system");
- File jobDir = new File(systemDir, "job");
- jobDir.mkdirs();
- mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), "jobs");
-
- mJobSet = new JobSet();
-
- // If the current RTC is earlier than the timestamp on our persisted jobs file,
- // we suspect that the RTC is uninitialized and so we cannot draw conclusions
- // about persisted job scheduling.
- //
- // Note that if the persisted jobs file does not exist, we proceed with the
- // assumption that the RTC is good. This is less work and is safe: if the
- // clock updates to sanity then we'll be saving the persisted jobs file in that
- // correct state, which is normal; or we'll wind up writing the jobs file with
- // an incorrect historical timestamp. That's fine; at worst we'll reboot with
- // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
- // settle into normal operation.
- mXmlTimestamp = mJobsFile.getLastModifiedTime();
- mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
-
- readJobMapFromDisk(mJobSet, mRtcGood);
- }
-
- public boolean jobTimesInflatedValid() {
- return mRtcGood;
- }
-
- public boolean clockNowValidToInflate(long now) {
- return now >= mXmlTimestamp;
- }
-
- /**
- * Find all the jobs that were affected by RTC clock uncertainty at boot time. Returns
- * parallel lists of the existing JobStatus objects and of new, equivalent JobStatus instances
- * with now-corrected time bounds.
- */
- public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
- final ArrayList<JobStatus> toRemove) {
- final long elapsedNow = sElapsedRealtimeClock.millis();
- final IActivityManager am = ActivityManager.getService();
-
- // Find the jobs that need to be fixed up, collecting them for post-iteration
- // replacement with their new versions
- forEachJob(job -> {
- final Pair<Long, Long> utcTimes = job.getPersistedUtcTimes();
- if (utcTimes != null) {
- Pair<Long, Long> elapsedRuntimes =
- convertRtcBoundsToElapsed(utcTimes, elapsedNow);
- JobStatus newJob = new JobStatus(job, job.getBaseHeartbeat(),
- elapsedRuntimes.first, elapsedRuntimes.second,
- 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
- newJob.prepareLocked(am);
- toAdd.add(newJob);
- toRemove.add(job);
- }
- });
- }
-
- /**
- * Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
- * it will be replaced.
- * @param jobStatus Job to add.
- * @return Whether or not an equivalent JobStatus was replaced by this operation.
- */
- public boolean add(JobStatus jobStatus) {
- boolean replaced = mJobSet.remove(jobStatus);
- mJobSet.add(jobStatus);
- if (jobStatus.isPersisted()) {
- maybeWriteStatusToDiskAsync();
- }
- if (DEBUG) {
- Slog.d(TAG, "Added job status to store: " + jobStatus);
- }
- return replaced;
- }
-
- boolean containsJob(JobStatus jobStatus) {
- return mJobSet.contains(jobStatus);
- }
-
- public int size() {
- return mJobSet.size();
- }
-
- public JobStorePersistStats getPersistStats() {
- return mPersistInfo;
- }
-
- public int countJobsForUid(int uid) {
- return mJobSet.countJobsForUid(uid);
- }
-
- /**
- * Remove the provided job. Will also delete the job if it was persisted.
- * @param removeFromPersisted If true, the job will be removed from the persisted job list
- * immediately (if it was persisted).
- * @return Whether or not the job existed to be removed.
- */
- public boolean remove(JobStatus jobStatus, boolean removeFromPersisted) {
- boolean removed = mJobSet.remove(jobStatus);
- if (!removed) {
- if (DEBUG) {
- Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus);
- }
- return false;
- }
- if (removeFromPersisted && jobStatus.isPersisted()) {
- maybeWriteStatusToDiskAsync();
- }
- return removed;
- }
-
- /**
- * Remove the jobs of users not specified in the whitelist.
- * @param whitelist Array of User IDs whose jobs are not to be removed.
- */
- public void removeJobsOfNonUsers(int[] whitelist) {
- mJobSet.removeJobsOfNonUsers(whitelist);
- }
-
- @VisibleForTesting
- public void clear() {
- mJobSet.clear();
- maybeWriteStatusToDiskAsync();
- }
-
- /**
- * @param userHandle User for whom we are querying the list of jobs.
- * @return A list of all the jobs scheduled for the provided user. Never null.
- */
- public List<JobStatus> getJobsByUser(int userHandle) {
- return mJobSet.getJobsByUser(userHandle);
- }
-
- /**
- * @param uid Uid of the requesting app.
- * @return All JobStatus objects for a given uid from the master list. Never null.
- */
- public List<JobStatus> getJobsByUid(int uid) {
- return mJobSet.getJobsByUid(uid);
- }
-
- /**
- * @param uid Uid of the requesting app.
- * @param jobId Job id, specified at schedule-time.
- * @return the JobStatus that matches the provided uId and jobId, or null if none found.
- */
- public JobStatus getJobByUidAndJobId(int uid, int jobId) {
- return mJobSet.get(uid, jobId);
- }
-
- /**
- * Iterate over the set of all jobs, invoking the supplied functor on each. This is for
- * customers who need to examine each job; we'd much rather not have to generate
- * transient unified collections for them to iterate over and then discard, or creating
- * iterators every time a client needs to perform a sweep.
- */
- public void forEachJob(Consumer<JobStatus> functor) {
- mJobSet.forEachJob(null, functor);
- }
-
- public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
- Consumer<JobStatus> functor) {
- mJobSet.forEachJob(filterPredicate, functor);
- }
-
- public void forEachJob(int uid, Consumer<JobStatus> functor) {
- mJobSet.forEachJob(uid, functor);
- }
-
- public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
- mJobSet.forEachJobForSourceUid(sourceUid, functor);
- }
-
- /** Version of the db schema. */
- private static final int JOBS_FILE_VERSION = 0;
- /** Tag corresponds to constraints this job needs. */
- private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
- /** Tag corresponds to execution parameters. */
- private static final String XML_TAG_PERIODIC = "periodic";
- private static final String XML_TAG_ONEOFF = "one-off";
- private static final String XML_TAG_EXTRAS = "extras";
-
- /**
- * Every time the state changes we write all the jobs in one swath, instead of trying to
- * track incremental changes.
- */
- private void maybeWriteStatusToDiskAsync() {
- synchronized (mWriteScheduleLock) {
- if (!mWriteScheduled) {
- if (DEBUG) {
- Slog.v(TAG, "Scheduling persist of jobs to disk.");
- }
- mIoHandler.postDelayed(mWriteRunnable, JOB_PERSIST_DELAY);
- mWriteScheduled = mWriteInProgress = true;
- }
- }
- }
-
- @VisibleForTesting
- public void readJobMapFromDisk(JobSet jobSet, boolean rtcGood) {
- new ReadJobMapFromDiskRunnable(jobSet, rtcGood).run();
- }
-
- /** Write persisted JobStore state to disk synchronously. Should only be used for testing. */
- @VisibleForTesting
- public void writeStatusToDiskForTesting() {
- synchronized (mWriteScheduleLock) {
- if (mWriteScheduled) {
- throw new IllegalStateException("An asynchronous write is already scheduled.");
- }
-
- mWriteScheduled = mWriteInProgress = true;
- mWriteRunnable.run();
- }
- }
-
- /**
- * Wait for any pending write to the persistent store to clear
- * @param maxWaitMillis Maximum time from present to wait
- * @return {@code true} if I/O cleared as expected, {@code false} if the wait
- * timed out before the pending write completed.
- */
- @VisibleForTesting
- public boolean waitForWriteToCompleteForTesting(long maxWaitMillis) {
- final long start = SystemClock.uptimeMillis();
- final long end = start + maxWaitMillis;
- synchronized (mWriteScheduleLock) {
- while (mWriteInProgress) {
- final long now = SystemClock.uptimeMillis();
- if (now >= end) {
- // still not done and we've hit the end; failure
- return false;
- }
- try {
- mWriteScheduleLock.wait(now - start + maxWaitMillis);
- } catch (InterruptedException e) {
- // Spurious; keep waiting
- break;
- }
- }
- }
- return true;
- }
-
- /**
- * Runnable that writes {@link #mJobSet} out to xml.
- * NOTE: This Runnable locks on mLock
- */
- private final Runnable mWriteRunnable = new Runnable() {
- @Override
- public void run() {
- final long startElapsed = sElapsedRealtimeClock.millis();
- final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
- // Intentionally allow new scheduling of a write operation *before* we clone
- // the job set. If we reset it to false after cloning, there's a window in
- // which no new write will be scheduled but mLock is not held, i.e. a new
- // job might appear and fail to be recognized as needing a persist. The
- // potential cost is one redundant write of an identical set of jobs in the
- // rare case of that specific race, but by doing it this way we avoid quite
- // a bit of lock contention.
- synchronized (mWriteScheduleLock) {
- mWriteScheduled = false;
- }
- synchronized (mLock) {
- // Clone the jobs so we can release the lock before writing.
- mJobSet.forEachJob(null, (job) -> {
- if (job.isPersisted()) {
- storeCopy.add(new JobStatus(job));
- }
- });
- }
- writeJobsMapImpl(storeCopy);
- if (DEBUG) {
- Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
- - startElapsed) + "ms");
- }
- synchronized (mWriteScheduleLock) {
- mWriteInProgress = false;
- mWriteScheduleLock.notifyAll();
- }
- }
-
- private void writeJobsMapImpl(List<JobStatus> jobList) {
- int numJobs = 0;
- int numSystemJobs = 0;
- int numSyncJobs = 0;
- try {
- final long startTime = SystemClock.uptimeMillis();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(baos, StandardCharsets.UTF_8.name());
- out.startDocument(null, true);
- out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
- out.startTag(null, "job-info");
- out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
- for (int i=0; i<jobList.size(); i++) {
- JobStatus jobStatus = jobList.get(i);
- if (DEBUG) {
- Slog.d(TAG, "Saving job " + jobStatus.getJobId());
- }
- out.startTag(null, "job");
- addAttributesToJobTag(out, jobStatus);
- writeConstraintsToXml(out, jobStatus);
- writeExecutionCriteriaToXml(out, jobStatus);
- writeBundleToXml(jobStatus.getJob().getExtras(), out);
- out.endTag(null, "job");
-
- numJobs++;
- if (jobStatus.getUid() == Process.SYSTEM_UID) {
- numSystemJobs++;
- if (isSyncJob(jobStatus)) {
- numSyncJobs++;
- }
- }
- }
- out.endTag(null, "job-info");
- out.endDocument();
-
- // Write out to disk in one fell swoop.
- FileOutputStream fos = mJobsFile.startWrite(startTime);
- fos.write(baos.toByteArray());
- mJobsFile.finishWrite(fos);
- } catch (IOException e) {
- if (DEBUG) {
- Slog.v(TAG, "Error writing out job data.", e);
- }
- } catch (XmlPullParserException e) {
- if (DEBUG) {
- Slog.d(TAG, "Error persisting bundle.", e);
- }
- } finally {
- mPersistInfo.countAllJobsSaved = numJobs;
- mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
- mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
- }
- }
-
- /** Write out a tag with data comprising the required fields and priority of this job and
- * its client.
- */
- private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
- throws IOException {
- out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
- out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
- out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
- if (jobStatus.getSourcePackageName() != null) {
- out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
- }
- if (jobStatus.getSourceTag() != null) {
- out.attribute(null, "sourceTag", jobStatus.getSourceTag());
- }
- out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
- out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
- out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
- out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
- if (jobStatus.getInternalFlags() != 0) {
- out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
- }
-
- out.attribute(null, "lastSuccessfulRunTime",
- String.valueOf(jobStatus.getLastSuccessfulRunTime()));
- out.attribute(null, "lastFailedRunTime",
- String.valueOf(jobStatus.getLastFailedRunTime()));
- }
-
- private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
- throws IOException, XmlPullParserException {
- out.startTag(null, XML_TAG_EXTRAS);
- PersistableBundle extrasCopy = deepCopyBundle(extras, 10);
- extrasCopy.saveToXml(out);
- out.endTag(null, XML_TAG_EXTRAS);
- }
-
- private PersistableBundle deepCopyBundle(PersistableBundle bundle, int maxDepth) {
- if (maxDepth <= 0) {
- return null;
- }
- PersistableBundle copy = (PersistableBundle) bundle.clone();
- Set<String> keySet = bundle.keySet();
- for (String key: keySet) {
- Object o = copy.get(key);
- if (o instanceof PersistableBundle) {
- PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1);
- copy.putPersistableBundle(key, bCopy);
- }
- }
- return copy;
- }
-
- /**
- * Write out a tag with data identifying this job's constraints. If the constraint isn't here
- * it doesn't apply.
- */
- private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
- out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
- if (jobStatus.hasConnectivityConstraint()) {
- final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
- out.attribute(null, "net-capabilities", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getCapabilities())));
- out.attribute(null, "net-unwanted-capabilities", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities())));
-
- out.attribute(null, "net-transport-types", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
- }
- if (jobStatus.hasIdleConstraint()) {
- out.attribute(null, "idle", Boolean.toString(true));
- }
- if (jobStatus.hasChargingConstraint()) {
- out.attribute(null, "charging", Boolean.toString(true));
- }
- if (jobStatus.hasBatteryNotLowConstraint()) {
- out.attribute(null, "battery-not-low", Boolean.toString(true));
- }
- if (jobStatus.hasStorageNotLowConstraint()) {
- out.attribute(null, "storage-not-low", Boolean.toString(true));
- }
- out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
- }
-
- private void writeExecutionCriteriaToXml(XmlSerializer out, JobStatus jobStatus)
- throws IOException {
- final JobInfo job = jobStatus.getJob();
- if (jobStatus.getJob().isPeriodic()) {
- out.startTag(null, XML_TAG_PERIODIC);
- out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
- out.attribute(null, "flex", Long.toString(job.getFlexMillis()));
- } else {
- out.startTag(null, XML_TAG_ONEOFF);
- }
-
- // If we still have the persisted times, we need to record those directly because
- // we haven't yet been able to calculate the usual elapsed-timebase bounds
- // correctly due to wall-clock uncertainty.
- Pair <Long, Long> utcJobTimes = jobStatus.getPersistedUtcTimes();
- if (DEBUG && utcJobTimes != null) {
- Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
- }
-
- final long nowRTC = sSystemClock.millis();
- final long nowElapsed = sElapsedRealtimeClock.millis();
- if (jobStatus.hasDeadlineConstraint()) {
- // Wall clock deadline.
- final long deadlineWallclock = (utcJobTimes == null)
- ? nowRTC + (jobStatus.getLatestRunTimeElapsed() - nowElapsed)
- : utcJobTimes.second;
- out.attribute(null, "deadline", Long.toString(deadlineWallclock));
- }
- if (jobStatus.hasTimingDelayConstraint()) {
- final long delayWallclock = (utcJobTimes == null)
- ? nowRTC + (jobStatus.getEarliestRunTime() - nowElapsed)
- : utcJobTimes.first;
- out.attribute(null, "delay", Long.toString(delayWallclock));
- }
-
- // Only write out back-off policy if it differs from the default.
- // This also helps the case where the job is idle -> these aren't allowed to specify
- // back-off.
- if (jobStatus.getJob().getInitialBackoffMillis() != JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS
- || jobStatus.getJob().getBackoffPolicy() != JobInfo.DEFAULT_BACKOFF_POLICY) {
- out.attribute(null, "backoff-policy", Integer.toString(job.getBackoffPolicy()));
- out.attribute(null, "initial-backoff", Long.toString(job.getInitialBackoffMillis()));
- }
- if (job.isPeriodic()) {
- out.endTag(null, XML_TAG_PERIODIC);
- } else {
- out.endTag(null, XML_TAG_ONEOFF);
- }
- }
- };
-
- /**
- * Translate the supplied RTC times to the elapsed timebase, with clamping appropriate
- * to interpreting them as a job's delay + deadline times for alarm-setting purposes.
- * @param rtcTimes a Pair<Long, Long> in which {@code first} is the "delay" earliest
- * allowable runtime for the job, and {@code second} is the "deadline" time at which
- * the job becomes overdue.
- */
- private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
- long nowElapsed) {
- final long nowWallclock = sSystemClock.millis();
- final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
- ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
- : JobStatus.NO_EARLIEST_RUNTIME;
- final long latest = (rtcTimes.second < JobStatus.NO_LATEST_RUNTIME)
- ? nowElapsed + Math.max(rtcTimes.second - nowWallclock, 0)
- : JobStatus.NO_LATEST_RUNTIME;
- return Pair.create(earliest, latest);
- }
-
- private static boolean isSyncJob(JobStatus status) {
- return com.android.server.content.SyncJobService.class.getName()
- .equals(status.getServiceComponent().getClassName());
- }
-
- /**
- * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
- * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
- */
- private final class ReadJobMapFromDiskRunnable implements Runnable {
- private final JobSet jobSet;
- private final boolean rtcGood;
-
- /**
- * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
- * so that after disk read we can populate it directly.
- */
- ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood) {
- this.jobSet = jobSet;
- this.rtcGood = rtcIsGood;
- }
-
- @Override
- public void run() {
- int numJobs = 0;
- int numSystemJobs = 0;
- int numSyncJobs = 0;
- try {
- List<JobStatus> jobs;
- FileInputStream fis = mJobsFile.openRead();
- synchronized (mLock) {
- jobs = readJobMapImpl(fis, rtcGood);
- if (jobs != null) {
- long now = sElapsedRealtimeClock.millis();
- IActivityManager am = ActivityManager.getService();
- for (int i=0; i<jobs.size(); i++) {
- JobStatus js = jobs.get(i);
- js.prepareLocked(am);
- js.enqueueTime = now;
- this.jobSet.add(js);
-
- numJobs++;
- if (js.getUid() == Process.SYSTEM_UID) {
- numSystemJobs++;
- if (isSyncJob(js)) {
- numSyncJobs++;
- }
- }
- }
- }
- }
- fis.close();
- } catch (FileNotFoundException e) {
- if (DEBUG) {
- Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
- }
- } catch (XmlPullParserException | IOException e) {
- Slog.wtf(TAG, "Error jobstore xml.", e);
- } finally {
- if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
- mPersistInfo.countAllJobsLoaded = numJobs;
- mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
- mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
- }
- }
- Slog.i(TAG, "Read " + numJobs + " jobs");
- }
-
- private List<JobStatus> readJobMapImpl(FileInputStream fis, boolean rtcIsGood)
- throws XmlPullParserException, IOException {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, StandardCharsets.UTF_8.name());
-
- int eventType = parser.getEventType();
- while (eventType != XmlPullParser.START_TAG &&
- eventType != XmlPullParser.END_DOCUMENT) {
- eventType = parser.next();
- Slog.d(TAG, "Start tag: " + parser.getName());
- }
- if (eventType == XmlPullParser.END_DOCUMENT) {
- if (DEBUG) {
- Slog.d(TAG, "No persisted jobs.");
- }
- return null;
- }
-
- String tagName = parser.getName();
- if ("job-info".equals(tagName)) {
- final List<JobStatus> jobs = new ArrayList<JobStatus>();
- // Read in version info.
- try {
- int version = Integer.parseInt(parser.getAttributeValue(null, "version"));
- if (version != JOBS_FILE_VERSION) {
- Slog.d(TAG, "Invalid version number, aborting jobs file read.");
- return null;
- }
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Invalid version number, aborting jobs file read.");
- return null;
- }
- eventType = parser.next();
- do {
- // Read each <job/>
- if (eventType == XmlPullParser.START_TAG) {
- tagName = parser.getName();
- // Start reading job.
- if ("job".equals(tagName)) {
- JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser);
- if (persistedJob != null) {
- if (DEBUG) {
- Slog.d(TAG, "Read out " + persistedJob);
- }
- jobs.add(persistedJob);
- } else {
- Slog.d(TAG, "Error reading job from file.");
- }
- }
- }
- eventType = parser.next();
- } while (eventType != XmlPullParser.END_DOCUMENT);
- return jobs;
- }
- return null;
- }
-
- /**
- * @param parser Xml parser at the beginning of a "<job/>" tag. The next "parser.next()" call
- * will take the parser into the body of the job tag.
- * @return Newly instantiated job holding all the information we just read out of the xml tag.
- */
- private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser)
- throws XmlPullParserException, IOException {
- JobInfo.Builder jobBuilder;
- int uid, sourceUserId;
- long lastSuccessfulRunTime;
- long lastFailedRunTime;
- int internalFlags = 0;
-
- // Read out job identifier attributes and priority.
- try {
- jobBuilder = buildBuilderFromXml(parser);
- jobBuilder.setPersisted(true);
- uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
-
- String val = parser.getAttributeValue(null, "priority");
- if (val != null) {
- jobBuilder.setPriority(Integer.parseInt(val));
- }
- val = parser.getAttributeValue(null, "flags");
- if (val != null) {
- jobBuilder.setFlags(Integer.parseInt(val));
- }
- val = parser.getAttributeValue(null, "internalFlags");
- if (val != null) {
- internalFlags = Integer.parseInt(val);
- }
- val = parser.getAttributeValue(null, "sourceUserId");
- sourceUserId = val == null ? -1 : Integer.parseInt(val);
-
- val = parser.getAttributeValue(null, "lastSuccessfulRunTime");
- lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val);
-
- val = parser.getAttributeValue(null, "lastFailedRunTime");
- lastFailedRunTime = val == null ? 0 : Long.parseLong(val);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Error parsing job's required fields, skipping");
- return null;
- }
-
- String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
- final String sourceTag = parser.getAttributeValue(null, "sourceTag");
-
- int eventType;
- // Read out constraints tag.
- do {
- eventType = parser.next();
- } while (eventType == XmlPullParser.TEXT); // Push through to next START_TAG.
-
- if (!(eventType == XmlPullParser.START_TAG &&
- XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) {
- // Expecting a <constraints> start tag.
- return null;
- }
- try {
- buildConstraintsFromXml(jobBuilder, parser);
- } catch (NumberFormatException e) {
- Slog.d(TAG, "Error reading constraints, skipping.");
- return null;
- }
- parser.next(); // Consume </constraints>
-
- // Read out execution parameters tag.
- do {
- eventType = parser.next();
- } while (eventType == XmlPullParser.TEXT);
- if (eventType != XmlPullParser.START_TAG) {
- return null;
- }
-
- // Tuple of (earliest runtime, latest runtime) in UTC.
- final Pair<Long, Long> rtcRuntimes;
- try {
- rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
- } catch (NumberFormatException e) {
- if (DEBUG) {
- Slog.d(TAG, "Error parsing execution time parameters, skipping.");
- }
- return null;
- }
-
- final long elapsedNow = sElapsedRealtimeClock.millis();
- Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
-
- if (XML_TAG_PERIODIC.equals(parser.getName())) {
- try {
- String val = parser.getAttributeValue(null, "period");
- final long periodMillis = Long.parseLong(val);
- val = parser.getAttributeValue(null, "flex");
- final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
- jobBuilder.setPeriodic(periodMillis, flexMillis);
- // As a sanity check, cap the recreated run time to be no later than flex+period
- // from now. This is the latest the periodic could be pushed out. This could
- // happen if the periodic ran early (at flex time before period), and then the
- // device rebooted.
- if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
- final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
- + periodMillis;
- final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
- - flexMillis;
- Slog.w(TAG,
- String.format("Periodic job for uid='%d' persisted run-time is" +
- " too big [%s, %s]. Clamping to [%s,%s]",
- uid,
- DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000),
- DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000),
- DateUtils.formatElapsedTime(
- clampedEarlyRuntimeElapsed / 1000),
- DateUtils.formatElapsedTime(
- clampedLateRuntimeElapsed / 1000))
- );
- elapsedRuntimes =
- Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
- }
- } catch (NumberFormatException e) {
- Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
- return null;
- }
- } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
- try {
- if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
- jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
- }
- if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
- jobBuilder.setOverrideDeadline(
- elapsedRuntimes.second - elapsedNow);
- }
- } catch (NumberFormatException e) {
- Slog.d(TAG, "Error reading job execution criteria, skipping.");
- return null;
- }
- } else {
- if (DEBUG) {
- Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName());
- }
- // Expecting a parameters start tag.
- return null;
- }
- maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
-
- parser.nextTag(); // Consume parameters end tag.
-
- // Read out extras Bundle.
- do {
- eventType = parser.next();
- } while (eventType == XmlPullParser.TEXT);
- if (!(eventType == XmlPullParser.START_TAG
- && XML_TAG_EXTRAS.equals(parser.getName()))) {
- if (DEBUG) {
- Slog.d(TAG, "Error reading extras, skipping.");
- }
- return null;
- }
-
- PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
- jobBuilder.setExtras(extras);
- parser.nextTag(); // Consume </extras>
-
- final JobInfo builtJob;
- try {
- builtJob = jobBuilder.build();
- } catch (Exception e) {
- Slog.w(TAG, "Unable to build job from XML, ignoring: "
- + jobBuilder.summarize());
- return null;
- }
-
- // Migrate sync jobs forward from earlier, incomplete representation
- if ("android".equals(sourcePackageName)
- && extras != null
- && extras.getBoolean("SyncManagerJob", false)) {
- sourcePackageName = extras.getString("owningPackage", sourcePackageName);
- if (DEBUG) {
- Slog.i(TAG, "Fixing up sync job source package name from 'android' to '"
- + sourcePackageName + "'");
- }
- }
-
- // And now we're done
- JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
- final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
- sourceUserId, elapsedNow);
- long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
- JobStatus js = new JobStatus(
- jobBuilder.build(), uid, sourcePackageName, sourceUserId,
- appBucket, currentHeartbeat, sourceTag,
- elapsedRuntimes.first, elapsedRuntimes.second,
- lastSuccessfulRunTime, lastFailedRunTime,
- (rtcIsGood) ? null : rtcRuntimes, internalFlags);
- return js;
- }
-
- private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
- // Pull out required fields from <job> attributes.
- int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
- String packageName = parser.getAttributeValue(null, "package");
- String className = parser.getAttributeValue(null, "class");
- ComponentName cname = new ComponentName(packageName, className);
-
- return new JobInfo.Builder(jobId, cname);
- }
-
- private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
- String val;
-
- final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
- final String netUnwantedCapabilities = parser.getAttributeValue(
- null, "net-unwanted-capabilities");
- final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
- if (netCapabilities != null && netTransportTypes != null) {
- final NetworkRequest request = new NetworkRequest.Builder().build();
- final long unwantedCapabilities = netUnwantedCapabilities != null
- ? Long.parseLong(netUnwantedCapabilities)
- : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities());
-
- // We're okay throwing NFE here; caught by caller
- request.networkCapabilities.setCapabilities(
- BitUtils.unpackBits(Long.parseLong(netCapabilities)),
- BitUtils.unpackBits(unwantedCapabilities));
- request.networkCapabilities.setTransportTypes(
- BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
- jobBuilder.setRequiredNetwork(request);
- } else {
- // Read legacy values
- val = parser.getAttributeValue(null, "connectivity");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
- }
- val = parser.getAttributeValue(null, "metered");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
- }
- val = parser.getAttributeValue(null, "unmetered");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
- }
- val = parser.getAttributeValue(null, "not-roaming");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
- }
- }
-
- val = parser.getAttributeValue(null, "idle");
- if (val != null) {
- jobBuilder.setRequiresDeviceIdle(true);
- }
- val = parser.getAttributeValue(null, "charging");
- if (val != null) {
- jobBuilder.setRequiresCharging(true);
- }
- val = parser.getAttributeValue(null, "battery-not-low");
- if (val != null) {
- jobBuilder.setRequiresBatteryNotLow(true);
- }
- val = parser.getAttributeValue(null, "storage-not-low");
- if (val != null) {
- jobBuilder.setRequiresStorageNotLow(true);
- }
- }
-
- /**
- * Builds the back-off policy out of the params tag. These attributes may not exist, depending
- * on whether the back-off was set when the job was first scheduled.
- */
- private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
- String val = parser.getAttributeValue(null, "initial-backoff");
- if (val != null) {
- long initialBackoff = Long.parseLong(val);
- val = parser.getAttributeValue(null, "backoff-policy");
- int backoffPolicy = Integer.parseInt(val); // Will throw NFE which we catch higher up.
- jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
- }
- }
-
- /**
- * Extract a job's earliest/latest run time data from XML. These are returned in
- * unadjusted UTC wall clock time, because we do not yet know whether the system
- * clock is reliable for purposes of calculating deltas from 'now'.
- *
- * @param parser
- * @return A Pair of timestamps in UTC wall-clock time. The first is the earliest
- * time at which the job is to become runnable, and the second is the deadline at
- * which it becomes overdue to execute.
- * @throws NumberFormatException
- */
- private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
- throws NumberFormatException {
- String val;
- // Pull out execution time data.
- val = parser.getAttributeValue(null, "delay");
- final long earliestRunTimeRtc = (val != null)
- ? Long.parseLong(val)
- : JobStatus.NO_EARLIEST_RUNTIME;
- val = parser.getAttributeValue(null, "deadline");
- final long latestRunTimeRtc = (val != null)
- ? Long.parseLong(val)
- : JobStatus.NO_LATEST_RUNTIME;
- return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
- }
- }
-
- /** Set of all tracked jobs. */
- @VisibleForTesting
- public static final class JobSet {
- @VisibleForTesting // Key is the getUid() originator of the jobs in each sheaf
- final SparseArray<ArraySet<JobStatus>> mJobs;
-
- @VisibleForTesting // Same data but with the key as getSourceUid() of the jobs in each sheaf
- final SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
-
- public JobSet() {
- mJobs = new SparseArray<ArraySet<JobStatus>>();
- mJobsPerSourceUid = new SparseArray<>();
- }
-
- public List<JobStatus> getJobsByUid(int uid) {
- ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- if (jobs != null) {
- matchingJobs.addAll(jobs);
- }
- return matchingJobs;
- }
-
- // By user, not by uid, so we need to traverse by key and check
- public List<JobStatus> getJobsByUser(int userId) {
- final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
- for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
- final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
- if (jobs != null) {
- result.addAll(jobs);
- }
- }
- }
- return result;
- }
-
- public boolean add(JobStatus job) {
- final int uid = job.getUid();
- final int sourceUid = job.getSourceUid();
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- if (jobs == null) {
- jobs = new ArraySet<JobStatus>();
- mJobs.put(uid, jobs);
- }
- ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
- if (jobsForSourceUid == null) {
- jobsForSourceUid = new ArraySet<>();
- mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
- }
- final boolean added = jobs.add(job);
- final boolean addedInSource = jobsForSourceUid.add(job);
- if (added != addedInSource) {
- Slog.wtf(TAG, "mJobs and mJobsPerSourceUid mismatch; caller= " + added
- + " source= " + addedInSource);
- }
- return added || addedInSource;
- }
-
- public boolean remove(JobStatus job) {
- final int uid = job.getUid();
- final ArraySet<JobStatus> jobs = mJobs.get(uid);
- final int sourceUid = job.getSourceUid();
- final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
- final boolean didRemove = jobs != null && jobs.remove(job);
- final boolean sourceRemove = jobsForSourceUid != null && jobsForSourceUid.remove(job);
- if (didRemove != sourceRemove) {
- Slog.wtf(TAG, "Job presence mismatch; caller=" + didRemove
- + " source=" + sourceRemove);
- }
- if (didRemove || sourceRemove) {
- // no more jobs for this uid? let the now-empty set objects be GC'd.
- if (jobs != null && jobs.size() == 0) {
- mJobs.remove(uid);
- }
- if (jobsForSourceUid != null && jobsForSourceUid.size() == 0) {
- mJobsPerSourceUid.remove(sourceUid);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Removes the jobs of all users not specified by the whitelist of user ids.
- * This will remove jobs scheduled *by* non-existent users as well as jobs scheduled *for*
- * non-existent users
- */
- public void removeJobsOfNonUsers(final int[] whitelist) {
- final Predicate<JobStatus> noSourceUser =
- job -> !ArrayUtils.contains(whitelist, job.getSourceUserId());
- final Predicate<JobStatus> noCallingUser =
- job -> !ArrayUtils.contains(whitelist, job.getUserId());
- removeAll(noSourceUser.or(noCallingUser));
- }
-
- private void removeAll(Predicate<JobStatus> predicate) {
- for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
- final ArraySet<JobStatus> jobs = mJobs.valueAt(jobSetIndex);
- for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
- if (predicate.test(jobs.valueAt(jobIndex))) {
- jobs.removeAt(jobIndex);
- }
- }
- if (jobs.size() == 0) {
- mJobs.removeAt(jobSetIndex);
- }
- }
- for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
- final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(jobSetIndex);
- for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
- if (predicate.test(jobs.valueAt(jobIndex))) {
- jobs.removeAt(jobIndex);
- }
- }
- if (jobs.size() == 0) {
- mJobsPerSourceUid.removeAt(jobSetIndex);
- }
- }
- }
-
- public boolean contains(JobStatus job) {
- final int uid = job.getUid();
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- return jobs != null && jobs.contains(job);
- }
-
- public JobStatus get(int uid, int jobId) {
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- if (jobs != null) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.valueAt(i);
- if (job.getJobId() == jobId) {
- return job;
- }
- }
- }
- return null;
- }
-
- // Inefficient; use only for testing
- public List<JobStatus> getAllJobs() {
- ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
- for (int i = mJobs.size() - 1; i >= 0; i--) {
- ArraySet<JobStatus> jobs = mJobs.valueAt(i);
- if (jobs != null) {
- // Use a for loop over the ArraySet, so we don't need to make its
- // optional collection class iterator implementation or have to go
- // through a temporary array from toArray().
- for (int j = jobs.size() - 1; j >= 0; j--) {
- allJobs.add(jobs.valueAt(j));
- }
- }
- }
- return allJobs;
- }
-
- public void clear() {
- mJobs.clear();
- mJobsPerSourceUid.clear();
- }
-
- public int size() {
- int total = 0;
- for (int i = mJobs.size() - 1; i >= 0; i--) {
- total += mJobs.valueAt(i).size();
- }
- return total;
- }
-
- // We only want to count the jobs that this uid has scheduled on its own
- // behalf, not those that the app has scheduled on someone else's behalf.
- public int countJobsForUid(int uid) {
- int total = 0;
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- if (jobs != null) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.valueAt(i);
- if (job.getUid() == job.getSourceUid()) {
- total++;
- }
- }
- }
- return total;
- }
-
- public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
- Consumer<JobStatus> functor) {
- for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
- ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
- if (jobs != null) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- final JobStatus jobStatus = jobs.valueAt(i);
- if ((filterPredicate == null) || filterPredicate.test(jobStatus)) {
- functor.accept(jobStatus);
- }
- }
- }
- }
- }
-
- public void forEachJob(int callingUid, Consumer<JobStatus> functor) {
- ArraySet<JobStatus> jobs = mJobs.get(callingUid);
- if (jobs != null) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- functor.accept(jobs.valueAt(i));
- }
- }
- }
-
- public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
- final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
- if (jobs != null) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- functor.accept(jobs.valueAt(i));
- }
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/job/StateChangedListener.java b/services/core/java/com/android/server/job/StateChangedListener.java
deleted file mode 100644
index 87bfc27..0000000
--- a/services/core/java/com/android/server/job/StateChangedListener.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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;
-
-import com.android.server.job.controllers.JobStatus;
-
-/**
- * Interface through which a {@link com.android.server.job.controllers.StateController} informs
- * the {@link com.android.server.job.JobSchedulerService} that there are some tasks potentially
- * ready to be run.
- */
-public interface StateChangedListener {
- /**
- * Called by the controller to notify the JobManager that it should check on the state of a
- * task.
- */
- public void onControllerStateChanged();
-
- /**
- * Called by the controller to notify the JobManager that regardless of the state of the task,
- * it must be run immediately.
- * @param jobStatus The state of the task which is to be run immediately. <strong>null
- * indicates to the scheduler that any ready jobs should be flushed.</strong>
- */
- public void onRunJobNow(JobStatus jobStatus);
-
- public void onDeviceIdleStateChanged(boolean deviceIdle);
-}
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
deleted file mode 100644
index b698e5b..0000000
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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.job.controllers;
-
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-import com.android.server.AppStateTracker;
-import com.android.server.AppStateTracker.Listener;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobStore;
-import com.android.server.job.StateControllerProto;
-import com.android.server.job.StateControllerProto.BackgroundJobsController.TrackedJob;
-
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Tracks the following pieces of JobStatus state:
- *
- * - the CONSTRAINT_BACKGROUND_NOT_RESTRICTED general constraint bit, which
- * is used to selectively permit battery-saver exempted jobs to run; and
- *
- * - the uid-active boolean state expressed by the AppStateTracker. Jobs in 'active'
- * uids are inherently eligible to run jobs regardless of the uid's standby bucket.
- */
-public final class BackgroundJobsController extends StateController {
- private static final String TAG = "JobScheduler.Background";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- // Tri-state about possible "is this uid 'active'?" knowledge
- static final int UNKNOWN = 0;
- static final int KNOWN_ACTIVE = 1;
- static final int KNOWN_INACTIVE = 2;
-
- private final AppStateTracker mAppStateTracker;
-
- public BackgroundJobsController(JobSchedulerService service) {
- super(service);
-
- mAppStateTracker = Preconditions.checkNotNull(
- LocalServices.getService(AppStateTracker.class));
- mAppStateTracker.addListener(mForceAppStandbyListener);
- }
-
- @Override
- public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- updateSingleJobRestrictionLocked(jobStatus, UNKNOWN);
- }
-
- @Override
- public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
- boolean forUpdate) {
- }
-
- @Override
- public void dumpControllerStateLocked(final IndentingPrintWriter pw,
- final Predicate<JobStatus> predicate) {
- mAppStateTracker.dump(pw);
- pw.println();
-
- mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
- final int uid = jobStatus.getSourceUid();
- final String sourcePkg = jobStatus.getSourcePackageName();
- pw.print("#");
- jobStatus.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, uid);
- pw.print(mAppStateTracker.isUidActive(uid) ? " active" : " idle");
- if (mAppStateTracker.isUidPowerSaveWhitelisted(uid) ||
- mAppStateTracker.isUidTempPowerSaveWhitelisted(uid)) {
- pw.print(", whitelisted");
- }
- pw.print(": ");
- pw.print(sourcePkg);
-
- pw.print(" [RUN_ANY_IN_BACKGROUND ");
- pw.print(mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg)
- ? "allowed]" : "disallowed]");
-
- if ((jobStatus.satisfiedConstraints
- & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
- pw.println(" RUNNABLE");
- } else {
- pw.println(" WAITING");
- }
- });
- }
-
- @Override
- public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
- Predicate<JobStatus> predicate) {
- final long token = proto.start(fieldId);
- final long mToken = proto.start(StateControllerProto.BACKGROUND);
-
- mAppStateTracker.dumpProto(proto,
- StateControllerProto.BackgroundJobsController.FORCE_APP_STANDBY_TRACKER);
-
- mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
- final long jsToken =
- proto.start(StateControllerProto.BackgroundJobsController.TRACKED_JOBS);
-
- jobStatus.writeToShortProto(proto,
- TrackedJob.INFO);
- final int sourceUid = jobStatus.getSourceUid();
- proto.write(TrackedJob.SOURCE_UID, sourceUid);
- final String sourcePkg = jobStatus.getSourcePackageName();
- proto.write(TrackedJob.SOURCE_PACKAGE_NAME, sourcePkg);
-
- proto.write(TrackedJob.IS_IN_FOREGROUND,
- mAppStateTracker.isUidActive(sourceUid));
- proto.write(TrackedJob.IS_WHITELISTED,
- mAppStateTracker.isUidPowerSaveWhitelisted(sourceUid) ||
- mAppStateTracker.isUidTempPowerSaveWhitelisted(sourceUid));
-
- proto.write(
- TrackedJob.CAN_RUN_ANY_IN_BACKGROUND,
- mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(
- sourceUid, sourcePkg));
-
- proto.write(
- TrackedJob.ARE_CONSTRAINTS_SATISFIED,
- (jobStatus.satisfiedConstraints &
- JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0);
-
- proto.end(jsToken);
- });
-
- proto.end(mToken);
- proto.end(token);
- }
-
- private void updateAllJobRestrictionsLocked() {
- updateJobRestrictionsLocked(/*filterUid=*/ -1, UNKNOWN);
- }
-
- private void updateJobRestrictionsForUidLocked(int uid, boolean isActive) {
- updateJobRestrictionsLocked(uid, (isActive) ? KNOWN_ACTIVE : KNOWN_INACTIVE);
- }
-
- private void updateJobRestrictionsLocked(int filterUid, int newActiveState) {
- final UpdateJobFunctor updateTrackedJobs = new UpdateJobFunctor(newActiveState);
-
- final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;
-
- final JobStore store = mService.getJobStore();
- if (filterUid > 0) {
- store.forEachJobForSourceUid(filterUid, updateTrackedJobs);
- } else {
- store.forEachJob(updateTrackedJobs);
- }
-
- final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
- if (DEBUG) {
- Slog.d(TAG, String.format(
- "Job status updated: %d/%d checked/total jobs, %d us",
- updateTrackedJobs.mCheckedCount,
- updateTrackedJobs.mTotalCount,
- (time / 1000)
- ));
- }
-
- if (updateTrackedJobs.mChanged) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
-
- boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, int activeState) {
-
- final int uid = jobStatus.getSourceUid();
- final String packageName = jobStatus.getSourcePackageName();
-
- final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
- (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
- != 0);
-
- final boolean isActive;
- if (activeState == UNKNOWN) {
- isActive = mAppStateTracker.isUidActive(uid);
- } else {
- isActive = (activeState == KNOWN_ACTIVE);
- }
- boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
- didChange |= jobStatus.setUidActive(isActive);
- return didChange;
- }
-
- private final class UpdateJobFunctor implements Consumer<JobStatus> {
- final int activeState;
- boolean mChanged = false;
- int mTotalCount = 0;
- int mCheckedCount = 0;
-
- public UpdateJobFunctor(int newActiveState) {
- activeState = newActiveState;
- }
-
- @Override
- public void accept(JobStatus jobStatus) {
- mTotalCount++;
- mCheckedCount++;
- if (updateSingleJobRestrictionLocked(jobStatus, activeState)) {
- mChanged = true;
- }
- }
- }
-
- private final Listener mForceAppStandbyListener = new Listener() {
- @Override
- public void updateAllJobs() {
- synchronized (mLock) {
- updateAllJobRestrictionsLocked();
- }
- }
-
- @Override
- public void updateJobsForUid(int uid, boolean isActive) {
- synchronized (mLock) {
- updateJobRestrictionsForUidLocked(uid, isActive);
- }
- }
-
- @Override
- public void updateJobsForUidPackage(int uid, String packageName, boolean isActive) {
- synchronized (mLock) {
- updateJobRestrictionsForUidLocked(uid, isActive);
- }
- }
- };
-}
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
deleted file mode 100644
index 46658ad..0000000
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * 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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-
-import java.util.function.Predicate;
-
-/**
- * Simple controller that tracks whether the phone is charging or not. The phone is considered to
- * be charging when it's been plugged in for more than two minutes, and the system has broadcast
- * ACTION_BATTERY_OK.
- */
-public final class BatteryController extends StateController {
- private static final String TAG = "JobScheduler.Battery";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
- private ChargingTracker mChargeTracker;
-
- @VisibleForTesting
- public ChargingTracker getTracker() {
- return mChargeTracker;
- }
-
- public BatteryController(JobSchedulerService service) {
- super(service);
- mChargeTracker = new ChargingTracker();
- mChargeTracker.startTracking();
- }
-
- @Override
- public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
- if (taskStatus.hasPowerConstraint()) {
- mTrackedTasks.add(taskStatus);
- taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
- taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());
- taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow());
- }
- }
-
- @Override
- public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
- if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
- mTrackedTasks.remove(taskStatus);
- }
- }
-
- private void maybeReportNewChargingStateLocked() {
- final boolean stablePower = mChargeTracker.isOnStablePower();
- final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
- if (DEBUG) {
- Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
- }
- boolean reportChange = false;
- for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
- final JobStatus ts = mTrackedTasks.valueAt(i);
- boolean previous = ts.setChargingConstraintSatisfied(stablePower);
- if (previous != stablePower) {
- reportChange = true;
- }
- previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
- if (previous != batteryNotLow) {
- reportChange = true;
- }
- }
- if (stablePower || batteryNotLow) {
- // If one of our conditions has been satisfied, always schedule any newly ready jobs.
- mStateChangedListener.onRunJobNow(null);
- } else if (reportChange) {
- // Otherwise, just let the job scheduler know the state has changed and take care of it
- // as it thinks is best.
- mStateChangedListener.onControllerStateChanged();
- }
- }
-
- public final class ChargingTracker extends BroadcastReceiver {
- /**
- * Track whether we're "charging", where charging means that we're ready to commit to
- * doing work.
- */
- private boolean mCharging;
- /** Keep track of whether the battery is charged enough that we want to do work. */
- private boolean mBatteryHealthy;
- /** Sequence number of last broadcast. */
- private int mLastBatterySeq = -1;
-
- private BroadcastReceiver mMonitor;
-
- public ChargingTracker() {
- }
-
- public void startTracking() {
- IntentFilter filter = new IntentFilter();
-
- // Battery health.
- filter.addAction(Intent.ACTION_BATTERY_LOW);
- filter.addAction(Intent.ACTION_BATTERY_OKAY);
- // Charging/not charging.
- filter.addAction(BatteryManager.ACTION_CHARGING);
- filter.addAction(BatteryManager.ACTION_DISCHARGING);
- mContext.registerReceiver(this, filter);
-
- // Initialise tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
- mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
- }
-
- public void setMonitorBatteryLocked(boolean enabled) {
- if (enabled) {
- if (mMonitor == null) {
- mMonitor = new BroadcastReceiver() {
- @Override public void onReceive(Context context, Intent intent) {
- ChargingTracker.this.onReceive(context, intent);
- }
- };
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- mContext.registerReceiver(mMonitor, filter);
- }
- } else {
- if (mMonitor != null) {
- mContext.unregisterReceiver(mMonitor);
- mMonitor = null;
- }
- }
- }
-
- public boolean isOnStablePower() {
- return mCharging && mBatteryHealthy;
- }
-
- public boolean isBatteryNotLow() {
- return mBatteryHealthy;
- }
-
- public boolean isMonitoring() {
- return mMonitor != null;
- }
-
- public int getSeq() {
- return mLastBatterySeq;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- onReceiveInternal(intent);
- }
-
- @VisibleForTesting
- public void onReceiveInternal(Intent intent) {
- synchronized (mLock) {
- final String action = intent.getAction();
- if (Intent.ACTION_BATTERY_LOW.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Battery life too low to do work. @ "
- + sElapsedRealtimeClock.millis());
- }
- // If we get this action, the battery is discharging => it isn't plugged in so
- // there's no work to cancel. We track this variable for the case where it is
- // charging, but hasn't been for long enough to be healthy.
- mBatteryHealthy = false;
- maybeReportNewChargingStateLocked();
- } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Battery life healthy enough to do work. @ "
- + sElapsedRealtimeClock.millis());
- }
- mBatteryHealthy = true;
- maybeReportNewChargingStateLocked();
- } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Received charging intent, fired @ "
- + sElapsedRealtimeClock.millis());
- }
- mCharging = true;
- maybeReportNewChargingStateLocked();
- } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Disconnected from power.");
- }
- mCharging = false;
- maybeReportNewChargingStateLocked();
- }
- mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
- mLastBatterySeq);
- }
- }
- }
-
- @Override
- public void dumpControllerStateLocked(IndentingPrintWriter pw,
- Predicate<JobStatus> predicate) {
- pw.println("Stable power: " + mChargeTracker.isOnStablePower());
- pw.println("Not low: " + mChargeTracker.isBatteryNotLow());
-
- if (mChargeTracker.isMonitoring()) {
- pw.print("MONITORING: seq=");
- pw.println(mChargeTracker.getSeq());
- }
- 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.BATTERY);
-
- proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
- mChargeTracker.isOnStablePower());
- proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
- mChargeTracker.isBatteryNotLow());
-
- proto.write(StateControllerProto.BatteryController.IS_MONITORING,
- mChargeTracker.isMonitoring());
- proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
- mChargeTracker.getSeq());
-
- 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.BatteryController.TRACKED_JOBS);
- js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO);
- proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID,
- js.getSourceUid());
- proto.end(jsToken);
- }
-
- proto.end(mToken);
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
deleted file mode 100644
index f8cf6ae..0000000
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ /dev/null
@@ -1,694 +0,0 @@
-/*
- * 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 static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-
-import android.app.job.JobInfo;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.INetworkPolicyListener;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkPolicyManager;
-import android.net.NetworkRequest;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-import android.util.ArraySet;
-import android.util.DataUnit;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobSchedulerService.Constants;
-import com.android.server.job.JobServiceContext;
-import com.android.server.job.StateControllerProto;
-import com.android.server.net.NetworkPolicyManagerInternal;
-
-import java.util.Objects;
-import java.util.function.Predicate;
-
-/**
- * Handles changes in connectivity.
- * <p>
- * Each app can have a different default networks or different connectivity
- * status due to user-requested network policies, so we need to check
- * constraints on a per-UID basis.
- *
- * Test: atest com.android.server.job.controllers.ConnectivityControllerTest
- */
-public final class ConnectivityController extends StateController implements
- ConnectivityManager.OnNetworkActiveListener {
- private static final String TAG = "JobScheduler.Connectivity";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- private final ConnectivityManager mConnManager;
- private final NetworkPolicyManager mNetPolicyManager;
- private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
-
- /** List of tracked jobs keyed by source UID. */
- @GuardedBy("mLock")
- private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
-
- /**
- * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager
- * grant an exception to in the app standby chain.
- */
- @GuardedBy("mLock")
- private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();
-
- /** List of currently available networks. */
- @GuardedBy("mLock")
- private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();
-
- private boolean mUseQuotaLimit;
-
- private static final int MSG_DATA_SAVER_TOGGLED = 0;
- private static final int MSG_UID_RULES_CHANGES = 1;
- private static final int MSG_REEVALUATE_JOBS = 2;
-
- private final Handler mHandler;
-
- public ConnectivityController(JobSchedulerService service) {
- super(service);
- mHandler = new CcHandler(mContext.getMainLooper());
-
- mConnManager = mContext.getSystemService(ConnectivityManager.class);
- mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
- mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
-
- // We're interested in all network changes; internally we match these
- // network changes against the active network for each UID with jobs.
- final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
- mConnManager.registerNetworkCallback(request, mNetworkCallback);
-
- mNetPolicyManager.registerListener(mNetPolicyListener);
-
- mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
- }
-
- @GuardedBy("mLock")
- @Override
- public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if (jobStatus.hasConnectivityConstraint()) {
- updateConstraintsSatisfied(jobStatus);
- ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
- if (jobs == null) {
- jobs = new ArraySet<>();
- mTrackedJobs.put(jobStatus.getSourceUid(), jobs);
- }
- jobs.add(jobStatus);
- jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
- }
- }
-
- @GuardedBy("mLock")
- @Override
- public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
- boolean forUpdate) {
- if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
- ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
- if (jobs != null) {
- jobs.remove(jobStatus);
- }
- maybeRevokeStandbyExceptionLocked(jobStatus);
- }
- }
-
- @GuardedBy("mLock")
- @Override
- public void onConstantsUpdatedLocked() {
- if (mConstants.USE_HEARTBEATS) {
- // App idle exceptions are only requested for the rolling quota system.
- if (DEBUG) Slog.i(TAG, "Revoking all standby exceptions");
- for (int i = 0; i < mRequestedWhitelistJobs.size(); ++i) {
- int uid = mRequestedWhitelistJobs.keyAt(i);
- mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
- }
- mRequestedWhitelistJobs.clear();
- }
- if (mUseQuotaLimit == mConstants.USE_HEARTBEATS) {
- mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
- mHandler.obtainMessage(MSG_REEVALUATE_JOBS).sendToTarget();
- }
- }
-
- /**
- * Returns true if the job's requested network is available. This DOES NOT necesarilly mean
- * that the UID has been granted access to the network.
- */
- public boolean isNetworkAvailable(JobStatus job) {
- synchronized (mLock) {
- for (int i = 0; i < mAvailableNetworks.size(); ++i) {
- final Network network = mAvailableNetworks.valueAt(i);
- final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(
- network);
- final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
- if (DEBUG) {
- Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
- + " and capabilities " + capabilities + ". Satisfied=" + satisfied);
- }
- if (satisfied) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Request that NetworkPolicyManager grant an exception to the uid from its standby policy
- * chain.
- */
- @VisibleForTesting
- @GuardedBy("mLock")
- void requestStandbyExceptionLocked(JobStatus job) {
- final int uid = job.getSourceUid();
- // Need to call this before adding the job.
- final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid);
- ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
- if (jobs == null) {
- jobs = new ArraySet<JobStatus>();
- mRequestedWhitelistJobs.put(uid, jobs);
- }
- if (!jobs.add(job) || isExceptionRequested) {
- if (DEBUG) {
- Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested.");
- }
- return;
- }
- if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid);
- mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true);
- }
-
- /** Returns whether a standby exception has been requested for the UID. */
- @VisibleForTesting
- @GuardedBy("mLock")
- boolean isStandbyExceptionRequestedLocked(final int uid) {
- ArraySet jobs = mRequestedWhitelistJobs.get(uid);
- return jobs != null && jobs.size() > 0;
- }
-
- @VisibleForTesting
- @GuardedBy("mLock")
- boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) {
- final boolean networkAvailable = isNetworkAvailable(jobStatus);
- if (DEBUG) {
- Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString()
- + " networkAvailable=" + networkAvailable);
- }
- // If the network isn't available, then requesting an exception won't help.
-
- return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus,
- JobStatus.CONSTRAINT_CONNECTIVITY);
- }
-
- /**
- * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
- * thing stopping a job from running.
- */
- @GuardedBy("mLock")
- @Override
- public void evaluateStateLocked(JobStatus jobStatus) {
- if (mConstants.USE_HEARTBEATS) {
- // This should only be used for the rolling quota system.
- return;
- }
-
- if (!jobStatus.hasConnectivityConstraint()) {
- return;
- }
-
- // Always check the full job readiness stat in case the component has been disabled.
- if (wouldBeReadyWithConnectivityLocked(jobStatus)) {
- if (DEBUG) {
- Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
- }
- requestStandbyExceptionLocked(jobStatus);
- } else {
- if (DEBUG) {
- Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
- }
- maybeRevokeStandbyExceptionLocked(jobStatus);
- }
- }
-
- @GuardedBy("mLock")
- @Override
- public void reevaluateStateLocked(final int uid) {
- if (mConstants.USE_HEARTBEATS) {
- return;
- }
- // Check if we still need a connectivity exception in case the JobService was disabled.
- ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
- if (jobs == null) {
- return;
- }
- for (int i = jobs.size() - 1; i >= 0; i--) {
- evaluateStateLocked(jobs.valueAt(i));
- }
- }
-
- /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */
- @VisibleForTesting
- @GuardedBy("mLock")
- void maybeRevokeStandbyExceptionLocked(final JobStatus job) {
- final int uid = job.getSourceUid();
- if (!isStandbyExceptionRequestedLocked(uid)) {
- return;
- }
- ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
- if (jobs == null) {
- Slog.wtf(TAG,
- "maybeRevokeStandbyExceptionLocked found null jobs array even though a "
- + "standby exception has been requested.");
- return;
- }
- if (!jobs.remove(job) || jobs.size() > 0) {
- if (DEBUG) {
- Slog.i(TAG,
- "maybeRevokeStandbyExceptionLocked not revoking because there are still "
- + jobs.size() + " jobs left.");
- }
- return;
- }
- // No more jobs that need an exception.
- revokeStandbyExceptionLocked(uid);
- }
-
- /**
- * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain
- * for the uid.
- */
- @GuardedBy("mLock")
- private void revokeStandbyExceptionLocked(final int uid) {
- if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid);
- mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
- mRequestedWhitelistJobs.remove(uid);
- }
-
- @GuardedBy("mLock")
- @Override
- public void onAppRemovedLocked(String pkgName, int uid) {
- mTrackedJobs.delete(uid);
- }
-
- /**
- * Test to see if running the given job on the given network is insane.
- * <p>
- * For example, if a job is trying to send 10MB over a 128Kbps EDGE
- * connection, it would take 10.4 minutes, and has no chance of succeeding
- * before the job times out, so we'd be insane to try running it.
- */
- private boolean isInsane(JobStatus jobStatus, Network network,
- NetworkCapabilities capabilities, Constants constants) {
- final long maxJobExecutionTimeMs = mUseQuotaLimit
- ? mService.getMaxJobExecutionTimeMs(jobStatus)
- : JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
-
- final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
- if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
- // If we don't know the bandwidth, all we can do is hope the job finishes in time.
- if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
- if (estimatedMillis > maxJobExecutionTimeMs) {
- // If we'd never finish before the timeout, we'd be insane!
- Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
- + " kbps network would take " + estimatedMillis + "ms and job has "
- + maxJobExecutionTimeMs + "ms to run; that's insane!");
- return true;
- }
- }
- }
-
- final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
- if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
- // If we don't know the bandwidth, all we can do is hope the job finishes in time.
- if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
- if (estimatedMillis > maxJobExecutionTimeMs) {
- // If we'd never finish before the timeout, we'd be insane!
- Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
- + " kbps network would take " + estimatedMillis + "ms and job has "
- + maxJobExecutionTimeMs + "ms to run; that's insane!");
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
- NetworkCapabilities capabilities, Constants constants) {
- // If network is congested, and job is less than 50% through the
- // developer-requested window, then we're okay delaying the job.
- if (!capabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)) {
- return jobStatus.getFractionRunTime() < constants.CONN_CONGESTION_DELAY_FRAC;
- } else {
- return false;
- }
- }
-
- private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
- NetworkCapabilities capabilities, Constants constants) {
- return jobStatus.getJob().getRequiredNetwork().networkCapabilities
- .satisfiedByNetworkCapabilities(capabilities);
- }
-
- private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
- NetworkCapabilities capabilities, Constants constants) {
- // Only consider doing this for prefetching jobs
- if (!jobStatus.getJob().isPrefetch()) {
- return false;
- }
-
- // See if we match after relaxing any unmetered request
- final NetworkCapabilities relaxed = new NetworkCapabilities(
- jobStatus.getJob().getRequiredNetwork().networkCapabilities)
- .removeCapability(NET_CAPABILITY_NOT_METERED);
- if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
- // TODO: treat this as "maybe" response; need to check quotas
- return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
- } else {
- return false;
- }
- }
-
- @VisibleForTesting
- boolean isSatisfied(JobStatus jobStatus, Network network,
- NetworkCapabilities capabilities, Constants constants) {
- // Zeroth, we gotta have a network to think about being satisfied
- if (network == null || capabilities == null) return false;
-
- // First, are we insane?
- if (isInsane(jobStatus, network, capabilities, constants)) return false;
-
- // Second, is the network congested?
- if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
-
- // Third, is the network a strict match?
- if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
-
- // Third, is the network a relaxed match?
- if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
-
- return false;
- }
-
- private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
- final Network network = mConnManager.getActiveNetworkForUid(jobStatus.getSourceUid());
- final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
- return updateConstraintsSatisfied(jobStatus, network, capabilities);
- }
-
- private boolean updateConstraintsSatisfied(JobStatus jobStatus, Network network,
- NetworkCapabilities capabilities) {
- // TODO: consider matching against non-active networks
-
- final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
- final NetworkInfo info = mConnManager.getNetworkInfoForUid(network,
- jobStatus.getSourceUid(), ignoreBlocked);
-
- final boolean connected = (info != null) && info.isConnected();
- final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
-
- final boolean changed = jobStatus
- .setConnectivityConstraintSatisfied(connected && satisfied);
-
- // Pass along the evaluated network for job to use; prevents race
- // conditions as default routes change over time, and opens the door to
- // using non-default routes.
- jobStatus.network = network;
-
- if (DEBUG) {
- Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
- + " for " + jobStatus + ": connected=" + connected
- + " satisfied=" + satisfied);
- }
- return changed;
- }
-
- /**
- * Update any jobs tracked by this controller that match given filters.
- *
- * @param filterUid only update jobs belonging to this UID, or {@code -1} to
- * update all tracked jobs.
- * @param filterNetwork only update jobs that would use this
- * {@link Network}, or {@code null} to update all tracked jobs.
- */
- private void updateTrackedJobs(int filterUid, Network filterNetwork) {
- synchronized (mLock) {
- // Since this is a really hot codepath, temporarily cache any
- // answers that we get from ConnectivityManager.
- final SparseArray<NetworkCapabilities> networkToCapabilities = new SparseArray<>();
-
- boolean changed = false;
- if (filterUid == -1) {
- for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
- changed |= updateTrackedJobsLocked(mTrackedJobs.valueAt(i),
- filterNetwork, networkToCapabilities);
- }
- } else {
- changed = updateTrackedJobsLocked(mTrackedJobs.get(filterUid),
- filterNetwork, networkToCapabilities);
- }
- if (changed) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- }
-
- private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs, Network filterNetwork,
- SparseArray<NetworkCapabilities> networkToCapabilities) {
- if (jobs == null || jobs.size() == 0) {
- return false;
- }
-
- final Network network = mConnManager.getActiveNetworkForUid(jobs.valueAt(0).getSourceUid());
- final int netId = network != null ? network.netId : -1;
- NetworkCapabilities capabilities = networkToCapabilities.get(netId);
- if (capabilities == null) {
- capabilities = mConnManager.getNetworkCapabilities(network);
- networkToCapabilities.put(netId, capabilities);
- }
- final boolean networkMatch = (filterNetwork == null
- || Objects.equals(filterNetwork, network));
-
- boolean changed = false;
- for (int i = jobs.size() - 1; i >= 0; i--) {
- final JobStatus js = jobs.valueAt(i);
-
- // Update either when we have a network match, or when the
- // job hasn't yet been evaluated against the currently
- // active network; typically when we just lost a network.
- if (networkMatch || !Objects.equals(js.network, network)) {
- changed |= updateConstraintsSatisfied(js, network, capabilities);
- }
- }
- return changed;
- }
-
- /**
- * We know the network has just come up. We want to run any jobs that are ready.
- */
- @Override
- public void onNetworkActive() {
- synchronized (mLock) {
- for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
- for (int j = jobs.size() - 1; j >= 0; j--) {
- final JobStatus js = jobs.valueAt(j);
- if (js.isReady()) {
- if (DEBUG) {
- Slog.d(TAG, "Running " + js + " due to network activity.");
- }
- mStateChangedListener.onRunJobNow(js);
- }
- }
- }
- }
- }
-
- private final NetworkCallback mNetworkCallback = new NetworkCallback() {
- @Override
- public void onAvailable(Network network) {
- if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
- synchronized (mLock) {
- mAvailableNetworks.add(network);
- }
- }
-
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
- if (DEBUG) {
- Slog.v(TAG, "onCapabilitiesChanged: " + network);
- }
- updateTrackedJobs(-1, network);
- }
-
- @Override
- public void onLost(Network network) {
- if (DEBUG) {
- Slog.v(TAG, "onLost: " + network);
- }
- synchronized (mLock) {
- mAvailableNetworks.remove(network);
- }
- updateTrackedJobs(-1, network);
- }
- };
-
- private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
- @Override
- public void onRestrictBackgroundChanged(boolean restrictBackground) {
- if (DEBUG) {
- Slog.v(TAG, "onRestrictBackgroundChanged: " + restrictBackground);
- }
- mHandler.obtainMessage(MSG_DATA_SAVER_TOGGLED).sendToTarget();
- }
-
- @Override
- public void onUidRulesChanged(int uid, int uidRules) {
- if (DEBUG) {
- Slog.v(TAG, "onUidRulesChanged: " + uid);
- }
- mHandler.obtainMessage(MSG_UID_RULES_CHANGES, uid, 0).sendToTarget();
- }
- };
-
- private class CcHandler extends Handler {
- CcHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- synchronized (mLock) {
- switch (msg.what) {
- case MSG_DATA_SAVER_TOGGLED:
- updateTrackedJobs(-1, null);
- break;
- case MSG_UID_RULES_CHANGES:
- updateTrackedJobs(msg.arg1, null);
- break;
- case MSG_REEVALUATE_JOBS:
- updateTrackedJobs(-1, null);
- break;
- }
- }
- }
- };
-
- @GuardedBy("mLock")
- @Override
- public void dumpControllerStateLocked(IndentingPrintWriter pw,
- Predicate<JobStatus> predicate) {
- pw.print("mUseQuotaLimit="); pw.println(mUseQuotaLimit);
-
- if (mRequestedWhitelistJobs.size() > 0) {
- pw.print("Requested standby exceptions:");
- for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
- pw.print(" ");
- pw.print(mRequestedWhitelistJobs.keyAt(i));
- pw.print(" (");
- pw.print(mRequestedWhitelistJobs.valueAt(i).size());
- pw.print(" jobs)");
- }
- pw.println();
- }
- if (mAvailableNetworks.size() > 0) {
- pw.println("Available networks:");
- pw.increaseIndent();
- for (int i = 0; i < mAvailableNetworks.size(); i++) {
- pw.println(mAvailableNetworks.valueAt(i));
- }
- pw.decreaseIndent();
- } else {
- pw.println("No available networks");
- }
- for (int i = 0; i < mTrackedJobs.size(); i++) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
- for (int j = 0; j < jobs.size(); j++) {
- final JobStatus js = jobs.valueAt(j);
- if (!predicate.test(js)) {
- continue;
- }
- pw.print("#");
- js.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, js.getSourceUid());
- pw.print(": ");
- pw.print(js.getJob().getRequiredNetwork());
- pw.println();
- }
- }
- }
-
- @GuardedBy("mLock")
- @Override
- public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
- Predicate<JobStatus> predicate) {
- final long token = proto.start(fieldId);
- final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
-
- for (int i = 0; i < mTrackedJobs.size(); i++) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
- for (int j = 0; j < jobs.size(); j++) {
- final JobStatus js = jobs.valueAt(j);
- if (!predicate.test(js)) {
- continue;
- }
- final long jsToken = proto.start(
- StateControllerProto.ConnectivityController.TRACKED_JOBS);
- js.writeToShortProto(proto,
- StateControllerProto.ConnectivityController.TrackedJob.INFO);
- proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
- js.getSourceUid());
- NetworkRequest rn = js.getJob().getRequiredNetwork();
- if (rn != null) {
- rn.writeToProto(proto,
- StateControllerProto.ConnectivityController.TrackedJob
- .REQUIRED_NETWORK);
- }
- proto.end(jsToken);
- }
- }
-
- proto.end(mToken);
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
deleted file mode 100644
index a775cf5..0000000
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * Copyright (C) 2016 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.annotation.UserIdInt;
-import android.app.job.JobInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.TimeUtils;
-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.StateControllerProto.ContentObserverController.Observer.TriggerContentData;
-
-import java.util.ArrayList;
-import java.util.function.Predicate;
-
-/**
- * Controller for monitoring changes to content URIs through a ContentObserver.
- */
-public final class ContentObserverController extends StateController {
- private static final String TAG = "JobScheduler.ContentObserver";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- /**
- * Maximum number of changing URIs we will batch together to report.
- * XXX Should be smarter about this, restricting it by the maximum number
- * of characters we will retain.
- */
- private static final int MAX_URIS_REPORTED = 50;
-
- /**
- * At this point we consider it urgent to schedule the job ASAP.
- */
- private static final int URIS_URGENT_THRESHOLD = 40;
-
- final private ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
- /**
- * Per-userid {@link JobInfo.TriggerContentUri} keyed ContentObserver cache.
- */
- final SparseArray<ArrayMap<JobInfo.TriggerContentUri, ObserverInstance>> mObservers =
- new SparseArray<>();
- final Handler mHandler;
-
- public ContentObserverController(JobSchedulerService service) {
- super(service);
- mHandler = new Handler(mContext.getMainLooper());
- }
-
- @Override
- public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
- if (taskStatus.hasContentTriggerConstraint()) {
- if (taskStatus.contentObserverJobInstance == null) {
- taskStatus.contentObserverJobInstance = new JobInstance(taskStatus);
- }
- if (DEBUG) {
- Slog.i(TAG, "Tracking content-trigger job " + taskStatus);
- }
- mTrackedTasks.add(taskStatus);
- taskStatus.setTrackingController(JobStatus.TRACKING_CONTENT);
- boolean havePendingUris = false;
- // If there is a previous job associated with the new job, propagate over
- // any pending content URI trigger reports.
- if (taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
- havePendingUris = true;
- }
- // If we have previously reported changed authorities/uris, then we failed
- // to complete the job with them so will re-record them to report again.
- if (taskStatus.changedAuthorities != null) {
- havePendingUris = true;
- if (taskStatus.contentObserverJobInstance.mChangedAuthorities == null) {
- taskStatus.contentObserverJobInstance.mChangedAuthorities
- = new ArraySet<>();
- }
- for (String auth : taskStatus.changedAuthorities) {
- taskStatus.contentObserverJobInstance.mChangedAuthorities.add(auth);
- }
- if (taskStatus.changedUris != null) {
- if (taskStatus.contentObserverJobInstance.mChangedUris == null) {
- taskStatus.contentObserverJobInstance.mChangedUris = new ArraySet<>();
- }
- for (Uri uri : taskStatus.changedUris) {
- taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
- }
- }
- taskStatus.changedAuthorities = null;
- taskStatus.changedUris = null;
- }
- taskStatus.changedAuthorities = null;
- taskStatus.changedUris = null;
- taskStatus.setContentTriggerConstraintSatisfied(havePendingUris);
- }
- if (lastJob != null && lastJob.contentObserverJobInstance != null) {
- // And now we can detach the instance state from the last job.
- lastJob.contentObserverJobInstance.detachLocked();
- lastJob.contentObserverJobInstance = null;
- }
- }
-
- @Override
- public void prepareForExecutionLocked(JobStatus taskStatus) {
- if (taskStatus.hasContentTriggerConstraint()) {
- if (taskStatus.contentObserverJobInstance != null) {
- taskStatus.changedUris = taskStatus.contentObserverJobInstance.mChangedUris;
- taskStatus.changedAuthorities
- = taskStatus.contentObserverJobInstance.mChangedAuthorities;
- taskStatus.contentObserverJobInstance.mChangedUris = null;
- taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
- }
- }
- }
-
- @Override
- public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
- boolean forUpdate) {
- if (taskStatus.clearTrackingController(JobStatus.TRACKING_CONTENT)) {
- mTrackedTasks.remove(taskStatus);
- if (taskStatus.contentObserverJobInstance != null) {
- taskStatus.contentObserverJobInstance.unscheduleLocked();
- if (incomingJob != null) {
- if (taskStatus.contentObserverJobInstance != null
- && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
- // We are stopping this job, but it is going to be replaced by this given
- // incoming job. We want to propagate our state over to it, so we don't
- // lose any content changes that had happened since the last one started.
- // If there is a previous job associated with the new job, propagate over
- // any pending content URI trigger reports.
- if (incomingJob.contentObserverJobInstance == null) {
- incomingJob.contentObserverJobInstance = new JobInstance(incomingJob);
- }
- incomingJob.contentObserverJobInstance.mChangedAuthorities
- = taskStatus.contentObserverJobInstance.mChangedAuthorities;
- incomingJob.contentObserverJobInstance.mChangedUris
- = taskStatus.contentObserverJobInstance.mChangedUris;
- taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
- taskStatus.contentObserverJobInstance.mChangedUris = null;
- }
- // We won't detach the content observers here, because we want to
- // allow them to continue monitoring so we don't miss anything... and
- // since we are giving an incomingJob here, we know this will be
- // immediately followed by a start tracking of that job.
- } else {
- // But here there is no incomingJob, so nothing coming up, so time to detach.
- taskStatus.contentObserverJobInstance.detachLocked();
- taskStatus.contentObserverJobInstance = null;
- }
- }
- if (DEBUG) {
- Slog.i(TAG, "No longer tracking job " + taskStatus);
- }
- }
- }
-
- @Override
- public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) {
- if (failureToReschedule.hasContentTriggerConstraint()
- && newJob.hasContentTriggerConstraint()) {
- // Our job has failed, and we are scheduling a new job for it.
- // Copy the last reported content changes in to the new job, so when
- // we schedule the new one we will pick them up and report them again.
- newJob.changedAuthorities = failureToReschedule.changedAuthorities;
- newJob.changedUris = failureToReschedule.changedUris;
- }
- }
-
- final class ObserverInstance extends ContentObserver {
- final JobInfo.TriggerContentUri mUri;
- final @UserIdInt int mUserId;
- final ArraySet<JobInstance> mJobs = new ArraySet<>();
-
- public ObserverInstance(Handler handler, JobInfo.TriggerContentUri uri,
- @UserIdInt int userId) {
- super(handler);
- mUri = uri;
- mUserId = userId;
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (DEBUG) {
- Slog.i(TAG, "onChange(self=" + selfChange + ") for " + uri
- + " when mUri=" + mUri + " mUserId=" + mUserId);
- }
- synchronized (mLock) {
- final int N = mJobs.size();
- for (int i=0; i<N; i++) {
- JobInstance inst = mJobs.valueAt(i);
- if (inst.mChangedUris == null) {
- inst.mChangedUris = new ArraySet<>();
- }
- if (inst.mChangedUris.size() < MAX_URIS_REPORTED) {
- inst.mChangedUris.add(uri);
- }
- if (inst.mChangedAuthorities == null) {
- inst.mChangedAuthorities = new ArraySet<>();
- }
- inst.mChangedAuthorities.add(uri.getAuthority());
- inst.scheduleLocked();
- }
- }
- }
- }
-
- static final class TriggerRunnable implements Runnable {
- final JobInstance mInstance;
-
- TriggerRunnable(JobInstance instance) {
- mInstance = instance;
- }
-
- @Override public void run() {
- mInstance.trigger();
- }
- }
-
- final class JobInstance {
- final ArrayList<ObserverInstance> mMyObservers = new ArrayList<>();
- final JobStatus mJobStatus;
- final Runnable mExecuteRunner;
- final Runnable mTimeoutRunner;
- ArraySet<Uri> mChangedUris;
- ArraySet<String> mChangedAuthorities;
-
- boolean mTriggerPending;
-
- // This constructor must be called with the master job scheduler lock held.
- JobInstance(JobStatus jobStatus) {
- mJobStatus = jobStatus;
- mExecuteRunner = new TriggerRunnable(this);
- mTimeoutRunner = new TriggerRunnable(this);
- final JobInfo.TriggerContentUri[] uris = jobStatus.getJob().getTriggerContentUris();
- final int sourceUserId = jobStatus.getSourceUserId();
- ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
- mObservers.get(sourceUserId);
- if (observersOfUser == null) {
- observersOfUser = new ArrayMap<>();
- mObservers.put(sourceUserId, observersOfUser);
- }
- if (uris != null) {
- for (JobInfo.TriggerContentUri uri : uris) {
- ObserverInstance obs = observersOfUser.get(uri);
- if (obs == null) {
- obs = new ObserverInstance(mHandler, uri, jobStatus.getSourceUserId());
- observersOfUser.put(uri, obs);
- final boolean andDescendants = (uri.getFlags() &
- JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS) != 0;
- if (DEBUG) {
- Slog.v(TAG, "New observer " + obs + " for " + uri.getUri()
- + " andDescendants=" + andDescendants
- + " sourceUserId=" + sourceUserId);
- }
- mContext.getContentResolver().registerContentObserver(
- uri.getUri(),
- andDescendants,
- obs,
- sourceUserId
- );
- } else {
- if (DEBUG) {
- final boolean andDescendants = (uri.getFlags() &
- JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS) != 0;
- Slog.v(TAG, "Reusing existing observer " + obs + " for " + uri.getUri()
- + " andDescendants=" + andDescendants);
- }
- }
- obs.mJobs.add(this);
- mMyObservers.add(obs);
- }
- }
- }
-
- void trigger() {
- boolean reportChange = false;
- synchronized (mLock) {
- if (mTriggerPending) {
- if (mJobStatus.setContentTriggerConstraintSatisfied(true)) {
- reportChange = true;
- }
- unscheduleLocked();
- }
- }
- // Let the scheduler know that state has changed. This may or may not result in an
- // execution.
- if (reportChange) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
-
- void scheduleLocked() {
- if (!mTriggerPending) {
- mTriggerPending = true;
- mHandler.postDelayed(mTimeoutRunner, mJobStatus.getTriggerContentMaxDelay());
- }
- mHandler.removeCallbacks(mExecuteRunner);
- if (mChangedUris.size() >= URIS_URGENT_THRESHOLD) {
- // If we start getting near the limit, GO NOW!
- mHandler.post(mExecuteRunner);
- } else {
- mHandler.postDelayed(mExecuteRunner, mJobStatus.getTriggerContentUpdateDelay());
- }
- }
-
- void unscheduleLocked() {
- if (mTriggerPending) {
- mHandler.removeCallbacks(mExecuteRunner);
- mHandler.removeCallbacks(mTimeoutRunner);
- mTriggerPending = false;
- }
- }
-
- void detachLocked() {
- final int N = mMyObservers.size();
- for (int i=0; i<N; i++) {
- final ObserverInstance obs = mMyObservers.get(i);
- obs.mJobs.remove(this);
- if (obs.mJobs.size() == 0) {
- if (DEBUG) {
- Slog.i(TAG, "Unregistering observer " + obs + " for " + obs.mUri.getUri());
- }
- mContext.getContentResolver().unregisterContentObserver(obs);
- ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observerOfUser =
- mObservers.get(obs.mUserId);
- if (observerOfUser != null) {
- observerOfUser.remove(obs.mUri);
- }
- }
- }
- }
- }
-
- @Override
- public void dumpControllerStateLocked(IndentingPrintWriter pw,
- Predicate<JobStatus> predicate) {
- for (int i = 0; i < mTrackedTasks.size(); i++) {
- 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();
- }
- pw.println();
-
- int N = mObservers.size();
- if (N > 0) {
- pw.println("Observers:");
- pw.increaseIndent();
- for (int userIdx = 0; userIdx < N; userIdx++) {
- final int userId = mObservers.keyAt(userIdx);
- ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
- mObservers.get(userId);
- int numbOfObserversPerUser = observersOfUser.size();
- for (int observerIdx = 0 ; observerIdx < numbOfObserversPerUser; observerIdx++) {
- ObserverInstance obs = observersOfUser.valueAt(observerIdx);
- int M = obs.mJobs.size();
- boolean shouldDump = false;
- for (int j = 0; j < M; j++) {
- JobInstance inst = obs.mJobs.valueAt(j);
- if (predicate.test(inst.mJobStatus)) {
- shouldDump = true;
- break;
- }
- }
- if (!shouldDump) {
- continue;
- }
- JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
- pw.print(trigger.getUri());
- pw.print(" 0x");
- pw.print(Integer.toHexString(trigger.getFlags()));
- pw.print(" (");
- pw.print(System.identityHashCode(obs));
- pw.println("):");
- pw.increaseIndent();
- pw.println("Jobs:");
- pw.increaseIndent();
- for (int j = 0; j < M; j++) {
- JobInstance inst = obs.mJobs.valueAt(j);
- pw.print("#");
- inst.mJobStatus.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, inst.mJobStatus.getSourceUid());
- if (inst.mChangedAuthorities != null) {
- pw.println(":");
- pw.increaseIndent();
- if (inst.mTriggerPending) {
- pw.print("Trigger pending: update=");
- TimeUtils.formatDuration(
- inst.mJobStatus.getTriggerContentUpdateDelay(), pw);
- pw.print(", max=");
- TimeUtils.formatDuration(
- inst.mJobStatus.getTriggerContentMaxDelay(), pw);
- pw.println();
- }
- pw.println("Changed Authorities:");
- for (int k = 0; k < inst.mChangedAuthorities.size(); k++) {
- pw.println(inst.mChangedAuthorities.valueAt(k));
- }
- if (inst.mChangedUris != null) {
- pw.println(" Changed URIs:");
- for (int k = 0; k < inst.mChangedUris.size(); k++) {
- pw.println(inst.mChangedUris.valueAt(k));
- }
- }
- pw.decreaseIndent();
- } else {
- pw.println();
- }
- }
- pw.decreaseIndent();
- pw.decreaseIndent();
- }
- }
- pw.decreaseIndent();
- }
- }
-
- @Override
- public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
- Predicate<JobStatus> predicate) {
- final long token = proto.start(fieldId);
- final long mToken = proto.start(StateControllerProto.CONTENT_OBSERVER);
-
- for (int i = 0; i < mTrackedTasks.size(); i++) {
- JobStatus js = mTrackedTasks.valueAt(i);
- if (!predicate.test(js)) {
- continue;
- }
- final long jsToken =
- proto.start(StateControllerProto.ContentObserverController.TRACKED_JOBS);
- js.writeToShortProto(proto,
- StateControllerProto.ContentObserverController.TrackedJob.INFO);
- proto.write(StateControllerProto.ContentObserverController.TrackedJob.SOURCE_UID,
- js.getSourceUid());
- proto.end(jsToken);
- }
-
- final int n = mObservers.size();
- for (int userIdx = 0; userIdx < n; userIdx++) {
- final long oToken =
- proto.start(StateControllerProto.ContentObserverController.OBSERVERS);
- final int userId = mObservers.keyAt(userIdx);
-
- proto.write(StateControllerProto.ContentObserverController.Observer.USER_ID, userId);
-
- ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
- mObservers.get(userId);
- int numbOfObserversPerUser = observersOfUser.size();
- for (int observerIdx = 0 ; observerIdx < numbOfObserversPerUser; observerIdx++) {
- ObserverInstance obs = observersOfUser.valueAt(observerIdx);
- int m = obs.mJobs.size();
- boolean shouldDump = false;
- for (int j = 0; j < m; j++) {
- JobInstance inst = obs.mJobs.valueAt(j);
- if (predicate.test(inst.mJobStatus)) {
- shouldDump = true;
- break;
- }
- }
- if (!shouldDump) {
- continue;
- }
- final long tToken = proto.start(
- StateControllerProto.ContentObserverController.Observer.TRIGGERS);
-
- JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
- Uri u = trigger.getUri();
- if (u != null) {
- proto.write(TriggerContentData.URI, u.toString());
- }
- proto.write(TriggerContentData.FLAGS, trigger.getFlags());
-
- for (int j = 0; j < m; j++) {
- final long jToken = proto.start(TriggerContentData.JOBS);
- JobInstance inst = obs.mJobs.valueAt(j);
-
- inst.mJobStatus.writeToShortProto(proto, TriggerContentData.JobInstance.INFO);
- proto.write(TriggerContentData.JobInstance.SOURCE_UID,
- inst.mJobStatus.getSourceUid());
-
- if (inst.mChangedAuthorities == null) {
- proto.end(jToken);
- continue;
- }
- if (inst.mTriggerPending) {
- proto.write(TriggerContentData.JobInstance.TRIGGER_CONTENT_UPDATE_DELAY_MS,
- inst.mJobStatus.getTriggerContentUpdateDelay());
- proto.write(TriggerContentData.JobInstance.TRIGGER_CONTENT_MAX_DELAY_MS,
- inst.mJobStatus.getTriggerContentMaxDelay());
- }
- for (int k = 0; k < inst.mChangedAuthorities.size(); k++) {
- proto.write(TriggerContentData.JobInstance.CHANGED_AUTHORITIES,
- inst.mChangedAuthorities.valueAt(k));
- }
- if (inst.mChangedUris != null) {
- for (int k = 0; k < inst.mChangedUris.size(); k++) {
- u = inst.mChangedUris.valueAt(k);
- if (u != null) {
- proto.write(TriggerContentData.JobInstance.CHANGED_URIS,
- u.toString());
- }
- }
- }
-
- proto.end(jToken);
- }
-
- proto.end(tToken);
- }
-
- proto.end(oToken);
- }
-
- proto.end(mToken);
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
deleted file mode 100644
index 127a5c8..0000000
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright (C) 2016 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.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.DeviceIdleController;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-import com.android.server.job.StateControllerProto.DeviceIdleJobsController.TrackedJob;
-
-import java.util.Arrays;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
- * When device is not dozing, set constraint for all jobs as satisfied.
- */
-public final class DeviceIdleJobsController extends StateController {
- private static final String TAG = "JobScheduler.DeviceIdle";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- private static final long BACKGROUND_JOBS_DELAY = 3000;
-
- static final int PROCESS_BACKGROUND_JOBS = 1;
-
- /**
- * These are jobs added with a special flag to indicate that they should be exempted from doze
- * when the app is temp whitelisted or in the foreground.
- */
- private final ArraySet<JobStatus> mAllowInIdleJobs;
- private final SparseBooleanArray mForegroundUids;
- private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
- private final DeviceIdleJobsDelayHandler mHandler;
- private final PowerManager mPowerManager;
- private final DeviceIdleController.LocalService mLocalDeviceIdleController;
-
- /**
- * True when in device idle mode, so we don't want to schedule any jobs.
- */
- private boolean mDeviceIdleMode;
- private int[] mDeviceIdleWhitelistAppIds;
- private int[] mPowerSaveTempWhitelistAppIds;
-
- // onReceive
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
- case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
- updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
- || mPowerManager.isLightDeviceIdleMode()));
- break;
- case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
- synchronized (mLock) {
- mDeviceIdleWhitelistAppIds =
- mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
- if (DEBUG) {
- Slog.d(TAG, "Got whitelist "
- + Arrays.toString(mDeviceIdleWhitelistAppIds));
- }
- }
- break;
- case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
- synchronized (mLock) {
- mPowerSaveTempWhitelistAppIds =
- mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
- if (DEBUG) {
- Slog.d(TAG, "Got temp whitelist "
- + Arrays.toString(mPowerSaveTempWhitelistAppIds));
- }
- boolean changed = false;
- for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
- changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
- }
- if (changed) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- break;
- }
- }
- };
-
- public DeviceIdleJobsController(JobSchedulerService service) {
- super(service);
-
- mHandler = new DeviceIdleJobsDelayHandler(mContext.getMainLooper());
- // Register for device idle mode changes
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mLocalDeviceIdleController =
- LocalServices.getService(DeviceIdleController.LocalService.class);
- mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
- mPowerSaveTempWhitelistAppIds =
- mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
- mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
- mAllowInIdleJobs = new ArraySet<>();
- mForegroundUids = new SparseBooleanArray();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
- filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
- filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
- mContext.registerReceiverAsUser(
- mBroadcastReceiver, UserHandle.ALL, filter, null, null);
- }
-
- void updateIdleMode(boolean enabled) {
- boolean changed = false;
- synchronized (mLock) {
- if (mDeviceIdleMode != enabled) {
- changed = true;
- }
- mDeviceIdleMode = enabled;
- if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
- if (enabled) {
- mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
- mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
- } else {
- // When coming out of doze, process all foreground uids immediately, while others
- // will be processed after a delay of 3 seconds.
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) {
- mService.getJobStore().forEachJobForSourceUid(
- mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
- }
- }
- mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
- }
- }
- // Inform the job scheduler service about idle mode changes
- if (changed) {
- mStateChangedListener.onDeviceIdleStateChanged(enabled);
- }
- }
-
- /**
- * Called by jobscheduler service to report uid state changes between active and idle
- */
- public void setUidActiveLocked(int uid, boolean active) {
- final boolean changed = (active != mForegroundUids.get(uid));
- if (!changed) {
- return;
- }
- if (DEBUG) {
- Slog.d(TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
- }
- mForegroundUids.put(uid, active);
- mDeviceIdleUpdateFunctor.mChanged = false;
- mService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
- if (mDeviceIdleUpdateFunctor.mChanged) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
-
- /**
- * Checks if the given job's scheduling app id exists in the device idle user whitelist.
- */
- boolean isWhitelistedLocked(JobStatus job) {
- return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
- UserHandle.getAppId(job.getSourceUid())) >= 0;
- }
-
- /**
- * Checks if the given job's scheduling app id exists in the device idle temp whitelist.
- */
- boolean isTempWhitelistedLocked(JobStatus job) {
- return ArrayUtils.contains(mPowerSaveTempWhitelistAppIds,
- UserHandle.getAppId(job.getSourceUid()));
- }
-
- private boolean updateTaskStateLocked(JobStatus task) {
- final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
- && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
- final boolean whitelisted = isWhitelistedLocked(task);
- final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
- return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
- }
-
- @Override
- public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
- mAllowInIdleJobs.add(jobStatus);
- }
- updateTaskStateLocked(jobStatus);
- }
-
- @Override
- public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
- boolean forUpdate) {
- if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
- mAllowInIdleJobs.remove(jobStatus);
- }
- }
-
- @Override
- public void dumpControllerStateLocked(final IndentingPrintWriter pw,
- final Predicate<JobStatus> predicate) {
- pw.println("Idle mode: " + mDeviceIdleMode);
- pw.println();
-
- mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
- pw.print("#");
- jobStatus.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, jobStatus.getSourceUid());
- pw.print(": ");
- pw.print(jobStatus.getSourcePackageName());
- pw.print((jobStatus.satisfiedConstraints
- & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
- ? " RUNNABLE" : " WAITING");
- if (jobStatus.dozeWhitelisted) {
- pw.print(" WHITELISTED");
- }
- if (mAllowInIdleJobs.contains(jobStatus)) {
- pw.print(" ALLOWED_IN_DOZE");
- }
- 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.DEVICE_IDLE);
-
- proto.write(StateControllerProto.DeviceIdleJobsController.IS_DEVICE_IDLE_MODE,
- mDeviceIdleMode);
- mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
- final long jsToken =
- proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS);
-
- jobStatus.writeToShortProto(proto, TrackedJob.INFO);
- proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid());
- proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
- proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
- (jobStatus.satisfiedConstraints &
- JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
- proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
- proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
-
- proto.end(jsToken);
- });
-
- proto.end(mToken);
- proto.end(token);
- }
-
- final class DeviceIdleUpdateFunctor implements Consumer<JobStatus> {
- boolean mChanged;
-
- @Override
- public void accept(JobStatus jobStatus) {
- mChanged |= updateTaskStateLocked(jobStatus);
- }
- }
-
- final class DeviceIdleJobsDelayHandler extends Handler {
- public DeviceIdleJobsDelayHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case PROCESS_BACKGROUND_JOBS:
- // Just process all the jobs, the ones in foreground should already be running.
- synchronized (mLock) {
- mDeviceIdleUpdateFunctor.mChanged = false;
- mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
- if (mDeviceIdleUpdateFunctor.mChanged) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- break;
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
deleted file mode 100644
index e3c311f..0000000
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
deleted file mode 100644
index 6f2b334..0000000
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ /dev/null
@@ -1,1861 +0,0 @@
-/*
- * 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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.app.AppGlobals;
-import android.app.IActivityManager;
-import android.app.job.JobInfo;
-import android.app.job.JobWorkItem;
-import android.content.ClipData;
-import android.content.ComponentName;
-import android.net.Network;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.text.format.TimeMigrationUtils;
-import android.util.ArraySet;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.StatsLog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.server.LocalServices;
-import com.android.server.job.GrantedUriPermissions;
-import com.android.server.job.JobSchedulerInternal;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobServerProtoEnums;
-import com.android.server.job.JobStatusDumpProto;
-import com.android.server.job.JobStatusShortInfoProto;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Predicate;
-
-/**
- * Uniquely identifies a job internally.
- * Created from the public {@link android.app.job.JobInfo} object when it lands on the scheduler.
- * Contains current state of the requirements of the job, as well as a function to evaluate
- * whether it's ready to run.
- * This object is shared among the various controllers - hence why the different fields are atomic.
- * This isn't strictly necessary because each controller is only interested in a specific field,
- * and the receivers that are listening for global state change will all run on the main looper,
- * but we don't enforce that so this is safer.
- *
- * Test: atest com.android.server.job.controllers.JobStatusTest
- * @hide
- */
-public final class JobStatus {
- static final String TAG = "JobSchedulerService";
- static final boolean DEBUG = JobSchedulerService.DEBUG;
-
- public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
- public static final long NO_EARLIEST_RUNTIME = 0L;
-
- static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
- static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
- static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
- static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
- static final int CONSTRAINT_TIMING_DELAY = 1<<31;
- static final int CONSTRAINT_DEADLINE = 1<<30;
- static final int CONSTRAINT_CONNECTIVITY = 1<<28;
- static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
- static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
- static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
- static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
-
- /**
- * The constraints that we want to log to statsd.
- *
- * Constraints that can be inferred from other atoms have been excluded to avoid logging too
- * much information and to reduce redundancy:
- *
- * * CONSTRAINT_CHARGING can be inferred with PluggedStateChanged (Atom #32)
- * * CONSTRAINT_BATTERY_NOT_LOW can be inferred with BatteryLevelChanged (Atom #30)
- * * CONSTRAINT_CONNECTIVITY can be partially inferred with ConnectivityStateChanged
- * (Atom #98) and BatterySaverModeStateChanged (Atom #20).
- * * CONSTRAINT_DEVICE_NOT_DOZING can be mostly inferred with DeviceIdleModeStateChanged
- * (Atom #21)
- * * CONSTRAINT_BACKGROUND_NOT_RESTRICTED can be inferred with BatterySaverModeStateChanged
- * (Atom #20)
- */
- private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
- | CONSTRAINT_DEADLINE
- | CONSTRAINT_IDLE
- | CONSTRAINT_STORAGE_NOT_LOW
- | CONSTRAINT_TIMING_DELAY
- | CONSTRAINT_WITHIN_QUOTA;
-
- // TODO(b/129954980)
- private static final boolean STATS_LOG_ENABLED = false;
-
- // Soft override: ignore constraints like time that don't affect API availability
- public static final int OVERRIDE_SOFT = 1;
- // Full override: ignore all constraints including API-affecting like connectivity
- public static final int OVERRIDE_FULL = 2;
-
- /** If not specified, trigger update delay is 10 seconds. */
- public static final long DEFAULT_TRIGGER_UPDATE_DELAY = 10*1000;
-
- /** The minimum possible update delay is 1/2 second. */
- public static final long MIN_TRIGGER_UPDATE_DELAY = 500;
-
- /** If not specified, trigger maxumum delay is 2 minutes. */
- public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000;
-
- /** The minimum possible update delay is 1 second. */
- public static final long MIN_TRIGGER_MAX_DELAY = 1000;
-
- final JobInfo job;
- /**
- * Uid of the package requesting this job. This can differ from the "source"
- * uid when the job was scheduled on the app's behalf, such as with the jobs
- * that underly Sync Manager operation.
- */
- final int callingUid;
- final String batteryName;
-
- /**
- * Identity of the app in which the job is hosted.
- */
- final String sourcePackageName;
- final int sourceUserId;
- final int sourceUid;
- final String sourceTag;
-
- final String tag;
-
- private GrantedUriPermissions uriPerms;
- private boolean prepared;
-
- static final boolean DEBUG_PREPARE = true;
- private Throwable unpreparedPoint = null;
-
- /**
- * Earliest point in the future at which this job will be eligible to run. A value of 0
- * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
- */
- private final long earliestRunTimeElapsedMillis;
- /**
- * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE}
- * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
- */
- private final long latestRunTimeElapsedMillis;
-
- /**
- * Valid only for periodic jobs. The original latest point in the future at which this
- * job was expected to run.
- */
- private long mOriginalLatestRunTimeElapsedMillis;
-
- /** How many times this job has failed, used to compute back-off. */
- private final int numFailures;
-
- /**
- * Current standby heartbeat when this job was scheduled or last ran. Used to
- * pin the runnability check regardless of the job's app moving between buckets.
- */
- private final long baseHeartbeat;
-
- /**
- * Which app standby bucket this job's app is in. Updated when the app is moved to a
- * different bucket.
- */
- private int standbyBucket;
-
- /**
- * Debugging: timestamp if we ever defer this job based on standby bucketing, this
- * is when we did so.
- */
- private long whenStandbyDeferred;
-
- /** The first time this job was force batched. */
- private long mFirstForceBatchedTimeElapsed;
-
- // Constraints.
- final int requiredConstraints;
- private final int mRequiredConstraintsOfInterest;
- int satisfiedConstraints = 0;
- private int mSatisfiedConstraintsOfInterest = 0;
-
- // Set to true if doze constraint was satisfied due to app being whitelisted.
- public boolean dozeWhitelisted;
-
- // Set to true when the app is "active" per AppStateTracker
- public boolean uidActive;
-
- /**
- * Flag for {@link #trackingControllers}: the battery controller is currently tracking this job.
- */
- public static final int TRACKING_BATTERY = 1<<0;
- /**
- * Flag for {@link #trackingControllers}: the network connectivity controller is currently
- * tracking this job.
- */
- public static final int TRACKING_CONNECTIVITY = 1<<1;
- /**
- * Flag for {@link #trackingControllers}: the content observer controller is currently
- * tracking this job.
- */
- public static final int TRACKING_CONTENT = 1<<2;
- /**
- * Flag for {@link #trackingControllers}: the idle controller is currently tracking this job.
- */
- public static final int TRACKING_IDLE = 1<<3;
- /**
- * Flag for {@link #trackingControllers}: the storage controller is currently tracking this job.
- */
- public static final int TRACKING_STORAGE = 1<<4;
- /**
- * Flag for {@link #trackingControllers}: the time controller is currently tracking this job.
- */
- public static final int TRACKING_TIME = 1<<5;
- /**
- * Flag for {@link #trackingControllers}: the quota controller is currently tracking this job.
- */
- public static final int TRACKING_QUOTA = 1 << 6;
-
- /**
- * Bit mask of controllers that are currently tracking the job.
- */
- private int trackingControllers;
-
- /**
- * Flag for {@link #mInternalFlags}: this job was scheduled when the app that owns the job
- * service (not necessarily the caller) was in the foreground and the job has no time
- * constraints, which makes it exempted from the battery saver job restriction.
- *
- * @hide
- */
- public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
-
- /**
- * Versatile, persistable flags for a job that's updated within the system server,
- * as opposed to {@link JobInfo#flags} that's set by callers.
- */
- private int mInternalFlags;
-
- // These are filled in by controllers when preparing for execution.
- public ArraySet<Uri> changedUris;
- public ArraySet<String> changedAuthorities;
- public Network network;
-
- public int lastEvaluatedPriority;
-
- // If non-null, this is work that has been enqueued for the job.
- public ArrayList<JobWorkItem> pendingWork;
-
- // If non-null, this is work that is currently being executed.
- public ArrayList<JobWorkItem> executingWork;
-
- public int nextPendingWorkId = 1;
-
- // Used by shell commands
- public int overrideState = 0;
-
- // When this job was enqueued, for ordering. (in elapsedRealtimeMillis)
- public long enqueueTime;
-
- // Metrics about queue latency. (in uptimeMillis)
- public long madePending;
- public long madeActive;
-
- /**
- * Last time a job finished successfully for a periodic job, in the currentTimeMillis time,
- * for dumpsys.
- */
- private long mLastSuccessfulRunTime;
-
- /**
- * Last time a job finished unsuccessfully, in the currentTimeMillis time, for dumpsys.
- */
- private long mLastFailedRunTime;
-
- /**
- * Transient: when a job is inflated from disk before we have a reliable RTC clock time,
- * we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
- * store in UTC so that we can fix up the job's scheduling criteria once we get a good
- * wall-clock time. If we have to persist the job again before the clock has been updated,
- * we record these times again rather than calculating based on the earliest/latest elapsed
- * time base figures.
- *
- * 'first' is the earliest/delay time, and 'second' is the latest/deadline time.
- */
- private Pair<Long, Long> mPersistedUtcTimes;
-
- /**
- * For use only by ContentObserverController: state it is maintaining about content URIs
- * being observed.
- */
- ContentObserverController.JobInstance contentObserverJobInstance;
-
- private long mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
- private long mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
-
- /////// Booleans that track if a job is ready to run. They should be updated whenever dependent
- /////// states change.
-
- /**
- * The deadline for the job has passed. This is only good for non-periodic jobs. A periodic job
- * should only run if its constraints are satisfied.
- * Computed as: NOT periodic AND has deadline constraint AND deadline constraint satisfied.
- */
- private boolean mReadyDeadlineSatisfied;
-
- /**
- * The device isn't Dozing or this job will be in the foreground. This implicit constraint must
- * be satisfied.
- */
- private boolean mReadyNotDozing;
-
- /**
- * The job is not restricted from running in the background (due to Battery Saver). This
- * implicit constraint must be satisfied.
- */
- private boolean mReadyNotRestrictedInBg;
-
- /** The job is within its quota based on its standby bucket. */
- private boolean mReadyWithinQuota;
-
- /** Provide a handle to the service that this job will be run on. */
- public int getServiceToken() {
- return callingUid;
- }
-
- /**
- * Core constructor for JobStatus instances. All other ctors funnel down to this one.
- *
- * @param job The actual requested parameters for the job
- * @param callingUid Identity of the app that is scheduling the job. This may not be the
- * app in which the job is implemented; such as with sync jobs.
- * @param sourcePackageName The package name of the app in which the job will run.
- * @param sourceUserId The user in which the job will run
- * @param standbyBucket The standby bucket that the source package is currently assigned to,
- * cached here for speed of handling during runnability evaluations (and updated when bucket
- * assignments are changed)
- * @param heartbeat Timestamp of when the job was created, in the standby-related
- * timebase.
- * @param tag A string associated with the job for debugging/logging purposes.
- * @param numFailures Count of how many times this job has requested a reschedule because
- * its work was not yet finished.
- * @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
- * is to be considered runnable
- * @param latestRunTimeElapsedMillis Milestone: point in time at which the job will be
- * considered overdue
- * @param lastSuccessfulRunTime When did we last run this job to completion?
- * @param lastFailedRunTime When did we last run this job only to have it stop incomplete?
- * @param internalFlags Non-API property flags about this job
- */
- private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
- int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
- long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
- long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
- this.job = job;
- this.callingUid = callingUid;
- this.standbyBucket = standbyBucket;
- this.baseHeartbeat = heartbeat;
-
- int tempSourceUid = -1;
- if (sourceUserId != -1 && sourcePackageName != null) {
- try {
- tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
- sourceUserId);
- } catch (RemoteException ex) {
- // Can't happen, PackageManager runs in the same process.
- }
- }
- if (tempSourceUid == -1) {
- this.sourceUid = callingUid;
- this.sourceUserId = UserHandle.getUserId(callingUid);
- this.sourcePackageName = job.getService().getPackageName();
- this.sourceTag = null;
- } else {
- this.sourceUid = tempSourceUid;
- this.sourceUserId = sourceUserId;
- this.sourcePackageName = sourcePackageName;
- this.sourceTag = tag;
- }
-
- this.batteryName = this.sourceTag != null
- ? this.sourceTag + ":" + job.getService().getPackageName()
- : job.getService().flattenToShortString();
- this.tag = "*job*/" + this.batteryName;
-
- this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
- this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
- this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
- this.numFailures = numFailures;
-
- int requiredConstraints = job.getConstraintFlags();
- if (job.getRequiredNetwork() != null) {
- requiredConstraints |= CONSTRAINT_CONNECTIVITY;
- }
- if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
- requiredConstraints |= CONSTRAINT_TIMING_DELAY;
- }
- if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
- requiredConstraints |= CONSTRAINT_DEADLINE;
- }
- if (job.getTriggerContentUris() != null) {
- requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
- }
- this.requiredConstraints = requiredConstraints;
- mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
- mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-
- mLastSuccessfulRunTime = lastSuccessfulRunTime;
- mLastFailedRunTime = lastFailedRunTime;
-
- mInternalFlags = internalFlags;
-
- updateEstimatedNetworkBytesLocked();
-
- if (job.getRequiredNetwork() != null) {
- // Later, when we check if a given network satisfies the required
- // network, we need to know the UID that is requesting it, so push
- // our source UID into place.
- job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
- }
- }
-
- /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
- * so we preserve RTC window bounds if the source object has them. */
- public JobStatus(JobStatus jobStatus) {
- this(jobStatus.getJob(), jobStatus.getUid(),
- jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
- jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
- jobStatus.getSourceTag(), jobStatus.getNumFailures(),
- jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
- jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
- jobStatus.getInternalFlags());
- mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
- if (jobStatus.mPersistedUtcTimes != null) {
- if (DEBUG) {
- Slog.i(TAG, "Cloning job with persisted run times", new RuntimeException("here"));
- }
- }
- }
-
- /**
- * Create a new JobStatus that was loaded from disk. We ignore the provided
- * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
- * from the {@link com.android.server.job.JobStore} and still want to respect its
- * wallclock runtime rather than resetting it on every boot.
- * We consider a freshly loaded job to no longer be in back-off, and the associated
- * standby bucket is whatever the OS thinks it should be at this moment.
- */
- public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
- int standbyBucket, long baseHeartbeat, String sourceTag,
- long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
- long lastSuccessfulRunTime, long lastFailedRunTime,
- Pair<Long, Long> persistedExecutionTimesUTC,
- int innerFlags) {
- this(job, callingUid, sourcePkgName, sourceUserId,
- standbyBucket, baseHeartbeat,
- sourceTag, 0,
- earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
-
- // Only during initial inflation do we record the UTC-timebase execution bounds
- // read from the persistent store. If we ever have to recreate the JobStatus on
- // the fly, it means we're rescheduling the job; and this means that the calculated
- // elapsed timebase bounds intrinsically become correct.
- this.mPersistedUtcTimes = persistedExecutionTimesUTC;
- if (persistedExecutionTimesUTC != null) {
- if (DEBUG) {
- Slog.i(TAG, "+ restored job with RTC times because of bad boot clock");
- }
- }
- }
-
- /** Create a new job to be rescheduled with the provided parameters. */
- public JobStatus(JobStatus rescheduling, long newBaseHeartbeat,
- long newEarliestRuntimeElapsedMillis,
- long newLatestRuntimeElapsedMillis, int backoffAttempt,
- long lastSuccessfulRunTime, long lastFailedRunTime) {
- this(rescheduling.job, rescheduling.getUid(),
- rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
- rescheduling.getStandbyBucket(), newBaseHeartbeat,
- rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
- newLatestRuntimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
- }
-
- /**
- * Create a newly scheduled job.
- * @param callingUid Uid of the package that scheduled this job.
- * @param sourcePkg Package name of the app that will actually run the job. Null indicates
- * that the calling package is the source.
- * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
- * caller.
- */
- public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
- int sourceUserId, String tag) {
- final long elapsedNow = sElapsedRealtimeClock.millis();
- final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
- if (job.isPeriodic()) {
- // Make sure period is in the interval [min_possible_period, max_possible_period].
- final long period = Math.max(JobInfo.getMinPeriodMillis(),
- Math.min(JobSchedulerService.MAX_ALLOWED_PERIOD_MS, job.getIntervalMillis()));
- latestRunTimeElapsedMillis = elapsedNow + period;
- earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis
- // Make sure flex is in the interval [min_possible_flex, period].
- - Math.max(JobInfo.getMinFlexMillis(), Math.min(period, job.getFlexMillis()));
- } else {
- earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
- elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
- latestRunTimeElapsedMillis = job.hasLateConstraint() ?
- elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
- }
- String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();
-
- int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
- sourceUserId, elapsedNow);
- JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
- long currentHeartbeat = js != null
- ? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
- : 0;
- return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
- standbyBucket, currentHeartbeat, tag, 0,
- earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
- 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
- /*innerFlags=*/ 0);
- }
-
- public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) {
- if (pendingWork == null) {
- pendingWork = new ArrayList<>();
- }
- work.setWorkId(nextPendingWorkId);
- nextPendingWorkId++;
- if (work.getIntent() != null
- && GrantedUriPermissions.checkGrantFlags(work.getIntent().getFlags())) {
- work.setGrants(GrantedUriPermissions.createFromIntent(am, work.getIntent(), sourceUid,
- sourcePackageName, sourceUserId, toShortString()));
- }
- pendingWork.add(work);
- updateEstimatedNetworkBytesLocked();
- }
-
- public JobWorkItem dequeueWorkLocked() {
- if (pendingWork != null && pendingWork.size() > 0) {
- JobWorkItem work = pendingWork.remove(0);
- if (work != null) {
- if (executingWork == null) {
- executingWork = new ArrayList<>();
- }
- executingWork.add(work);
- work.bumpDeliveryCount();
- }
- updateEstimatedNetworkBytesLocked();
- return work;
- }
- return null;
- }
-
- public boolean hasWorkLocked() {
- return (pendingWork != null && pendingWork.size() > 0) || hasExecutingWorkLocked();
- }
-
- public boolean hasExecutingWorkLocked() {
- return executingWork != null && executingWork.size() > 0;
- }
-
- private static void ungrantWorkItem(IActivityManager am, JobWorkItem work) {
- if (work.getGrants() != null) {
- ((GrantedUriPermissions)work.getGrants()).revoke(am);
- }
- }
-
- public boolean completeWorkLocked(IActivityManager am, int workId) {
- if (executingWork != null) {
- final int N = executingWork.size();
- for (int i = 0; i < N; i++) {
- JobWorkItem work = executingWork.get(i);
- if (work.getWorkId() == workId) {
- executingWork.remove(i);
- ungrantWorkItem(am, work);
- return true;
- }
- }
- }
- return false;
- }
-
- private static void ungrantWorkList(IActivityManager am, ArrayList<JobWorkItem> list) {
- if (list != null) {
- final int N = list.size();
- for (int i = 0; i < N; i++) {
- ungrantWorkItem(am, list.get(i));
- }
- }
- }
-
- public void stopTrackingJobLocked(IActivityManager am, JobStatus incomingJob) {
- if (incomingJob != null) {
- // We are replacing with a new job -- transfer the work! We do any executing
- // work first, since that was originally at the front of the pending work.
- if (executingWork != null && executingWork.size() > 0) {
- incomingJob.pendingWork = executingWork;
- }
- if (incomingJob.pendingWork == null) {
- incomingJob.pendingWork = pendingWork;
- } else if (pendingWork != null && pendingWork.size() > 0) {
- incomingJob.pendingWork.addAll(pendingWork);
- }
- pendingWork = null;
- executingWork = null;
- incomingJob.nextPendingWorkId = nextPendingWorkId;
- incomingJob.updateEstimatedNetworkBytesLocked();
- } else {
- // We are completely stopping the job... need to clean up work.
- ungrantWorkList(am, pendingWork);
- pendingWork = null;
- ungrantWorkList(am, executingWork);
- executingWork = null;
- }
- updateEstimatedNetworkBytesLocked();
- }
-
- public void prepareLocked(IActivityManager am) {
- if (prepared) {
- Slog.wtf(TAG, "Already prepared: " + this);
- return;
- }
- prepared = true;
- if (DEBUG_PREPARE) {
- unpreparedPoint = null;
- }
- final ClipData clip = job.getClipData();
- if (clip != null) {
- uriPerms = GrantedUriPermissions.createFromClip(am, clip, sourceUid, sourcePackageName,
- sourceUserId, job.getClipGrantFlags(), toShortString());
- }
- }
-
- public void unprepareLocked(IActivityManager am) {
- if (!prepared) {
- Slog.wtf(TAG, "Hasn't been prepared: " + this);
- if (DEBUG_PREPARE && unpreparedPoint != null) {
- Slog.e(TAG, "Was already unprepared at ", unpreparedPoint);
- }
- return;
- }
- prepared = false;
- if (DEBUG_PREPARE) {
- unpreparedPoint = new Throwable().fillInStackTrace();
- }
- if (uriPerms != null) {
- uriPerms.revoke(am);
- uriPerms = null;
- }
- }
-
- public boolean isPreparedLocked() {
- return prepared;
- }
-
- public JobInfo getJob() {
- return job;
- }
-
- public int getJobId() {
- return job.getId();
- }
-
- public void printUniqueId(PrintWriter pw) {
- UserHandle.formatUid(pw, callingUid);
- pw.print("/");
- pw.print(job.getId());
- }
-
- public int getNumFailures() {
- return numFailures;
- }
-
- public ComponentName getServiceComponent() {
- return job.getService();
- }
-
- public String getSourcePackageName() {
- return sourcePackageName;
- }
-
- public int getSourceUid() {
- return sourceUid;
- }
-
- public int getSourceUserId() {
- return sourceUserId;
- }
-
- public int getUserId() {
- return UserHandle.getUserId(callingUid);
- }
-
- public int getStandbyBucket() {
- return standbyBucket;
- }
-
- public long getBaseHeartbeat() {
- return baseHeartbeat;
- }
-
- public void setStandbyBucket(int newBucket) {
- standbyBucket = newBucket;
- }
-
- // Called only by the standby monitoring code
- public long getWhenStandbyDeferred() {
- return whenStandbyDeferred;
- }
-
- // Called only by the standby monitoring code
- public void setWhenStandbyDeferred(long now) {
- whenStandbyDeferred = now;
- }
-
- /**
- * Returns the first time this job was force batched, in the elapsed realtime timebase. Will be
- * 0 if this job was never force batched.
- */
- public long getFirstForceBatchedTimeElapsed() {
- return mFirstForceBatchedTimeElapsed;
- }
-
- public void setFirstForceBatchedTimeElapsed(long now) {
- mFirstForceBatchedTimeElapsed = now;
- }
-
- public String getSourceTag() {
- return sourceTag;
- }
-
- public int getUid() {
- return callingUid;
- }
-
- public String getBatteryName() {
- return batteryName;
- }
-
- public String getTag() {
- return tag;
- }
-
- public int getPriority() {
- return job.getPriority();
- }
-
- public int getFlags() {
- return job.getFlags();
- }
-
- public int getInternalFlags() {
- return mInternalFlags;
- }
-
- public void addInternalFlags(int flags) {
- mInternalFlags |= flags;
- }
-
- public int getSatisfiedConstraintFlags() {
- return satisfiedConstraints;
- }
-
- public void maybeAddForegroundExemption(Predicate<Integer> uidForegroundChecker) {
- // Jobs with time constraints shouldn't be exempted.
- if (job.hasEarlyConstraint() || job.hasLateConstraint()) {
- return;
- }
- // Already exempted, skip the foreground check.
- if ((mInternalFlags & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
- return;
- }
- if (uidForegroundChecker.test(getSourceUid())) {
- addInternalFlags(INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
- }
- }
-
- private void updateEstimatedNetworkBytesLocked() {
- mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
- mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
-
- if (pendingWork != null) {
- for (int i = 0; i < pendingWork.size(); i++) {
- if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- // If any component of the job has unknown usage, we don't have a
- // complete picture of what data will be used, and we have to treat the
- // entire up/download as unknown.
- long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
- if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- mTotalNetworkDownloadBytes += downloadBytes;
- }
- }
- if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- // If any component of the job has unknown usage, we don't have a
- // complete picture of what data will be used, and we have to treat the
- // entire up/download as unknown.
- long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
- if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- mTotalNetworkUploadBytes += uploadBytes;
- }
- }
- }
- }
- }
-
- public long getEstimatedNetworkDownloadBytes() {
- return mTotalNetworkDownloadBytes;
- }
-
- public long getEstimatedNetworkUploadBytes() {
- return mTotalNetworkUploadBytes;
- }
-
- /** Does this job have any sort of networking constraint? */
- public boolean hasConnectivityConstraint() {
- return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
- }
-
- public boolean hasChargingConstraint() {
- return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
- }
-
- public boolean hasBatteryNotLowConstraint() {
- return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0;
- }
-
- public boolean hasPowerConstraint() {
- return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0;
- }
-
- public boolean hasStorageNotLowConstraint() {
- return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0;
- }
-
- public boolean hasTimingDelayConstraint() {
- return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0;
- }
-
- public boolean hasDeadlineConstraint() {
- return (requiredConstraints&CONSTRAINT_DEADLINE) != 0;
- }
-
- public boolean hasIdleConstraint() {
- return (requiredConstraints&CONSTRAINT_IDLE) != 0;
- }
-
- public boolean hasContentTriggerConstraint() {
- return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0;
- }
-
- public long getTriggerContentUpdateDelay() {
- long time = job.getTriggerContentUpdateDelay();
- if (time < 0) {
- return DEFAULT_TRIGGER_UPDATE_DELAY;
- }
- return Math.max(time, MIN_TRIGGER_UPDATE_DELAY);
- }
-
- public long getTriggerContentMaxDelay() {
- long time = job.getTriggerContentMaxDelay();
- if (time < 0) {
- return DEFAULT_TRIGGER_MAX_DELAY;
- }
- return Math.max(time, MIN_TRIGGER_MAX_DELAY);
- }
-
- public boolean isPersisted() {
- return job.isPersisted();
- }
-
- public long getEarliestRunTime() {
- return earliestRunTimeElapsedMillis;
- }
-
- public long getLatestRunTimeElapsed() {
- return latestRunTimeElapsedMillis;
- }
-
- public long getOriginalLatestRunTimeElapsed() {
- return mOriginalLatestRunTimeElapsedMillis;
- }
-
- public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
- mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
- }
-
- /**
- * Return the fractional position of "now" within the "run time" window of
- * this job.
- * <p>
- * For example, if the earliest run time was 10 minutes ago, and the latest
- * run time is 30 minutes from now, this would return 0.25.
- * <p>
- * If the job has no window defined, returns 1. When only an earliest or
- * latest time is defined, it's treated as an infinitely small window at
- * that time.
- */
- public float getFractionRunTime() {
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
- return 1;
- } else if (earliestRunTimeElapsedMillis == 0) {
- return now >= latestRunTimeElapsedMillis ? 1 : 0;
- } else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
- return now >= earliestRunTimeElapsedMillis ? 1 : 0;
- } else {
- if (now <= earliestRunTimeElapsedMillis) {
- return 0;
- } else if (now >= latestRunTimeElapsedMillis) {
- return 1;
- } else {
- return (float) (now - earliestRunTimeElapsedMillis)
- / (float) (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis);
- }
- }
- }
-
- public Pair<Long, Long> getPersistedUtcTimes() {
- return mPersistedUtcTimes;
- }
-
- public void clearPersistedUtcTimes() {
- mPersistedUtcTimes = null;
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setChargingConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setBatteryNotLowConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setStorageNotLowConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setTimingDelayConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setDeadlineConstraintSatisfied(boolean state) {
- if (setConstraintSatisfied(CONSTRAINT_DEADLINE, state)) {
- // The constraint was changed. Update the ready flag.
- mReadyDeadlineSatisfied = !job.isPeriodic() && hasDeadlineConstraint() && state;
- return true;
- }
- return false;
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setIdleConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_IDLE, state);
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setConnectivityConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setContentTriggerConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
- dozeWhitelisted = whitelisted;
- if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state)) {
- // The constraint was changed. Update the ready flag.
- mReadyNotDozing = state || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
- return true;
- }
- return false;
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setBackgroundNotRestrictedConstraintSatisfied(boolean state) {
- if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, state)) {
- // The constraint was changed. Update the ready flag.
- mReadyNotRestrictedInBg = state;
- return true;
- }
- return false;
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setQuotaConstraintSatisfied(boolean state) {
- if (setConstraintSatisfied(CONSTRAINT_WITHIN_QUOTA, state)) {
- // The constraint was changed. Update the ready flag.
- mReadyWithinQuota = state;
- return true;
- }
- return false;
- }
-
- /** @return true if the state was changed, false otherwise. */
- boolean setUidActive(final boolean newActiveState) {
- if (newActiveState != uidActive) {
- uidActive = newActiveState;
- return true;
- }
- return false; /* unchanged */
- }
-
- /** @return true if the constraint was changed, false otherwise. */
- boolean setConstraintSatisfied(int constraint, boolean state) {
- boolean old = (satisfiedConstraints&constraint) != 0;
- if (old == state) {
- return false;
- }
- satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
- mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
- if (STATS_LOG_ENABLED && (STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) {
- StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED,
- sourceUid, null, getBatteryName(), getProtoConstraint(constraint),
- state ? StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__SATISFIED
- : StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED);
- }
- return true;
- }
-
- boolean isConstraintSatisfied(int constraint) {
- return (satisfiedConstraints&constraint) != 0;
- }
-
- boolean clearTrackingController(int which) {
- if ((trackingControllers&which) != 0) {
- trackingControllers &= ~which;
- return true;
- }
- return false;
- }
-
- void setTrackingController(int which) {
- trackingControllers |= which;
- }
-
- public long getLastSuccessfulRunTime() {
- return mLastSuccessfulRunTime;
- }
-
- public long getLastFailedRunTime() {
- return mLastFailedRunTime;
- }
-
- /**
- * @return Whether or not this job is ready to run, based on its requirements.
- */
- public boolean isReady() {
- return isReady(mSatisfiedConstraintsOfInterest);
- }
-
- /**
- * @return Whether or not this job would be ready to run if it had the specified constraint
- * granted, based on its requirements.
- */
- boolean wouldBeReadyWithConstraint(int constraint) {
- boolean oldValue = false;
- int satisfied = mSatisfiedConstraintsOfInterest;
- switch (constraint) {
- case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
- oldValue = mReadyNotRestrictedInBg;
- mReadyNotRestrictedInBg = true;
- break;
- case CONSTRAINT_DEADLINE:
- oldValue = mReadyDeadlineSatisfied;
- mReadyDeadlineSatisfied = true;
- break;
- case CONSTRAINT_DEVICE_NOT_DOZING:
- oldValue = mReadyNotDozing;
- mReadyNotDozing = true;
- break;
- case CONSTRAINT_WITHIN_QUOTA:
- oldValue = mReadyWithinQuota;
- mReadyWithinQuota = true;
- break;
- default:
- satisfied |= constraint;
- break;
- }
-
- boolean toReturn = isReady(satisfied);
-
- switch (constraint) {
- case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
- mReadyNotRestrictedInBg = oldValue;
- break;
- case CONSTRAINT_DEADLINE:
- mReadyDeadlineSatisfied = oldValue;
- break;
- case CONSTRAINT_DEVICE_NOT_DOZING:
- mReadyNotDozing = oldValue;
- break;
- case CONSTRAINT_WITHIN_QUOTA:
- mReadyWithinQuota = oldValue;
- break;
- }
- return toReturn;
- }
-
- private boolean isReady(int satisfiedConstraints) {
- // Quota constraints trumps all other constraints.
- if (!mReadyWithinQuota) {
- return false;
- }
- // Deadline constraint trumps other constraints besides quota (except for periodic jobs
- // where deadline is an implementation detail. A periodic job should only run if its
- // constraints are satisfied).
- // DeviceNotDozing implicit constraint must be satisfied
- // NotRestrictedInBackground implicit constraint must be satisfied
- return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
- || isConstraintsSatisfied(satisfiedConstraints));
- }
-
- static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
- | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
- | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
-
- // Soft override covers all non-"functional" constraints
- static final int SOFT_OVERRIDE_CONSTRAINTS =
- CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
- | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
-
- /**
- * @return Whether the constraints set on this job are satisfied.
- */
- public boolean isConstraintsSatisfied() {
- return isConstraintsSatisfied(mSatisfiedConstraintsOfInterest);
- }
-
- private boolean isConstraintsSatisfied(int satisfiedConstraints) {
- if (overrideState == OVERRIDE_FULL) {
- // force override: the job is always runnable
- return true;
- }
-
- int sat = satisfiedConstraints;
- if (overrideState == OVERRIDE_SOFT) {
- // override: pretend all 'soft' requirements are satisfied
- sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
- }
-
- return (sat & mRequiredConstraintsOfInterest) == mRequiredConstraintsOfInterest;
- }
-
- public boolean matches(int uid, int jobId) {
- return this.job.getId() == jobId && this.callingUid == uid;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("JobStatus{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" #");
- UserHandle.formatUid(sb, callingUid);
- sb.append("/");
- sb.append(job.getId());
- sb.append(' ');
- sb.append(batteryName);
- sb.append(" u=");
- sb.append(getUserId());
- sb.append(" s=");
- sb.append(getSourceUid());
- if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
- || latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
- long now = sElapsedRealtimeClock.millis();
- sb.append(" TIME=");
- formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
- sb.append(":");
- formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
- }
- if (job.getRequiredNetwork() != null) {
- sb.append(" NET");
- }
- if (job.isRequireCharging()) {
- sb.append(" CHARGING");
- }
- if (job.isRequireBatteryNotLow()) {
- sb.append(" BATNOTLOW");
- }
- if (job.isRequireStorageNotLow()) {
- sb.append(" STORENOTLOW");
- }
- if (job.isRequireDeviceIdle()) {
- sb.append(" IDLE");
- }
- if (job.isPeriodic()) {
- sb.append(" PERIODIC");
- }
- if (job.isPersisted()) {
- sb.append(" PERSISTED");
- }
- if ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) == 0) {
- sb.append(" WAIT:DEV_NOT_DOZING");
- }
- if (job.getTriggerContentUris() != null) {
- sb.append(" URIS=");
- sb.append(Arrays.toString(job.getTriggerContentUris()));
- }
- if (numFailures != 0) {
- sb.append(" failures=");
- sb.append(numFailures);
- }
- if (isReady()) {
- sb.append(" READY");
- }
- sb.append("}");
- return sb.toString();
- }
-
- private void formatRunTime(PrintWriter pw, long runtime, long defaultValue, long now) {
- if (runtime == defaultValue) {
- pw.print("none");
- } else {
- TimeUtils.formatDuration(runtime - now, pw);
- }
- }
-
- private void formatRunTime(StringBuilder sb, long runtime, long defaultValue, long now) {
- if (runtime == defaultValue) {
- sb.append("none");
- } else {
- TimeUtils.formatDuration(runtime - now, sb);
- }
- }
-
- /**
- * Convenience function to identify a job uniquely without pulling all the data that
- * {@link #toString()} returns.
- */
- public String toShortString() {
- StringBuilder sb = new StringBuilder();
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" #");
- UserHandle.formatUid(sb, callingUid);
- sb.append("/");
- sb.append(job.getId());
- sb.append(' ');
- sb.append(batteryName);
- return sb.toString();
- }
-
- /**
- * Convenience function to identify a job uniquely without pulling all the data that
- * {@link #toString()} returns.
- */
- public String toShortStringExceptUniqueId() {
- StringBuilder sb = new StringBuilder();
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(' ');
- sb.append(batteryName);
- return sb.toString();
- }
-
- /**
- * Convenience function to dump data that identifies a job uniquely to proto. This is intended
- * to mimic {@link #toShortString}.
- */
- public void writeToShortProto(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(JobStatusShortInfoProto.CALLING_UID, callingUid);
- proto.write(JobStatusShortInfoProto.JOB_ID, job.getId());
- proto.write(JobStatusShortInfoProto.BATTERY_NAME, batteryName);
-
- proto.end(token);
- }
-
- void dumpConstraints(PrintWriter pw, int constraints) {
- if ((constraints&CONSTRAINT_CHARGING) != 0) {
- pw.print(" CHARGING");
- }
- if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) {
- pw.print(" BATTERY_NOT_LOW");
- }
- if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) {
- pw.print(" STORAGE_NOT_LOW");
- }
- if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) {
- pw.print(" TIMING_DELAY");
- }
- if ((constraints&CONSTRAINT_DEADLINE) != 0) {
- pw.print(" DEADLINE");
- }
- if ((constraints&CONSTRAINT_IDLE) != 0) {
- pw.print(" IDLE");
- }
- if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
- pw.print(" CONNECTIVITY");
- }
- if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) {
- pw.print(" CONTENT_TRIGGER");
- }
- if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
- pw.print(" DEVICE_NOT_DOZING");
- }
- if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
- pw.print(" BACKGROUND_NOT_RESTRICTED");
- }
- if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
- pw.print(" WITHIN_QUOTA");
- }
- if (constraints != 0) {
- pw.print(" [0x");
- pw.print(Integer.toHexString(constraints));
- pw.print("]");
- }
- }
-
- /** Returns a {@link JobServerProtoEnums.Constraint} enum value for the given constraint. */
- private int getProtoConstraint(int constraint) {
- switch (constraint) {
- case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
- return JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
- case CONSTRAINT_BATTERY_NOT_LOW:
- return JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW;
- case CONSTRAINT_CHARGING:
- return JobServerProtoEnums.CONSTRAINT_CHARGING;
- case CONSTRAINT_CONNECTIVITY:
- return JobServerProtoEnums.CONSTRAINT_CONNECTIVITY;
- case CONSTRAINT_CONTENT_TRIGGER:
- return JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER;
- case CONSTRAINT_DEADLINE:
- return JobServerProtoEnums.CONSTRAINT_DEADLINE;
- case CONSTRAINT_DEVICE_NOT_DOZING:
- return JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING;
- case CONSTRAINT_IDLE:
- return JobServerProtoEnums.CONSTRAINT_IDLE;
- case CONSTRAINT_STORAGE_NOT_LOW:
- return JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW;
- case CONSTRAINT_TIMING_DELAY:
- return JobServerProtoEnums.CONSTRAINT_TIMING_DELAY;
- case CONSTRAINT_WITHIN_QUOTA:
- return JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA;
- default:
- return JobServerProtoEnums.CONSTRAINT_UNKNOWN;
- }
- }
-
- /** Writes constraints to the given repeating proto field. */
- void dumpConstraints(ProtoOutputStream proto, long fieldId, int constraints) {
- if ((constraints & CONSTRAINT_CHARGING) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CHARGING);
- }
- if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW);
- }
- if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW);
- }
- if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_TIMING_DELAY);
- }
- if ((constraints & CONSTRAINT_DEADLINE) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEADLINE);
- }
- if ((constraints & CONSTRAINT_IDLE) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_IDLE);
- }
- if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONNECTIVITY);
- }
- if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER);
- }
- if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING);
- }
- if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA);
- }
- if ((constraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
- proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED);
- }
- }
-
- private void dumpJobWorkItem(PrintWriter pw, String prefix, JobWorkItem work, int index) {
- pw.print(prefix); pw.print(" #"); pw.print(index); pw.print(": #");
- pw.print(work.getWorkId()); pw.print(" "); pw.print(work.getDeliveryCount());
- pw.print("x "); pw.println(work.getIntent());
- if (work.getGrants() != null) {
- pw.print(prefix); pw.println(" URI grants:");
- ((GrantedUriPermissions)work.getGrants()).dump(pw, prefix + " ");
- }
- }
-
- private void dumpJobWorkItem(ProtoOutputStream proto, long fieldId, JobWorkItem work) {
- final long token = proto.start(fieldId);
-
- proto.write(JobStatusDumpProto.JobWorkItem.WORK_ID, work.getWorkId());
- proto.write(JobStatusDumpProto.JobWorkItem.DELIVERY_COUNT, work.getDeliveryCount());
- if (work.getIntent() != null) {
- work.getIntent().writeToProto(proto, JobStatusDumpProto.JobWorkItem.INTENT);
- }
- Object grants = work.getGrants();
- if (grants != null) {
- ((GrantedUriPermissions) grants).dump(proto, JobStatusDumpProto.JobWorkItem.URI_GRANTS);
- }
-
- proto.end(token);
- }
-
- /**
- * Returns a bucket name based on the normalized bucket indices, not the AppStandby constants.
- */
- String getBucketName() {
- return bucketName(standbyBucket);
- }
-
- /**
- * Returns a bucket name based on the normalized bucket indices, not the AppStandby constants.
- */
- static String bucketName(int standbyBucket) {
- switch (standbyBucket) {
- case 0: return "ACTIVE";
- case 1: return "WORKING_SET";
- case 2: return "FREQUENT";
- case 3: return "RARE";
- case 4: return "NEVER";
- default:
- return "Unknown: " + standbyBucket;
- }
- }
-
- // Dumpsys infrastructure
- public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
- pw.print(prefix); UserHandle.formatUid(pw, callingUid);
- pw.print(" tag="); pw.println(tag);
- pw.print(prefix);
- pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid());
- pw.print(" user="); pw.print(getSourceUserId());
- pw.print(" pkg="); pw.println(getSourcePackageName());
- if (full) {
- pw.print(prefix); pw.println("JobInfo:");
- pw.print(prefix); pw.print(" Service: ");
- pw.println(job.getService().flattenToShortString());
- if (job.isPeriodic()) {
- pw.print(prefix); pw.print(" PERIODIC: interval=");
- TimeUtils.formatDuration(job.getIntervalMillis(), pw);
- pw.print(" flex="); TimeUtils.formatDuration(job.getFlexMillis(), pw);
- pw.println();
- }
- if (job.isPersisted()) {
- pw.print(prefix); pw.println(" PERSISTED");
- }
- if (job.getPriority() != 0) {
- pw.print(prefix); pw.print(" Priority: ");
- pw.println(JobInfo.getPriorityString(job.getPriority()));
- }
- if (job.getFlags() != 0) {
- pw.print(prefix); pw.print(" Flags: ");
- pw.println(Integer.toHexString(job.getFlags()));
- }
- if (getInternalFlags() != 0) {
- pw.print(prefix); pw.print(" Internal flags: ");
- pw.print(Integer.toHexString(getInternalFlags()));
-
- if ((getInternalFlags()&INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
- pw.print(" HAS_FOREGROUND_EXEMPTION");
- }
- pw.println();
- }
- pw.print(prefix); pw.print(" Requires: charging=");
- pw.print(job.isRequireCharging()); pw.print(" batteryNotLow=");
- pw.print(job.isRequireBatteryNotLow()); pw.print(" deviceIdle=");
- pw.println(job.isRequireDeviceIdle());
- if (job.getTriggerContentUris() != null) {
- pw.print(prefix); pw.println(" Trigger content URIs:");
- for (int i = 0; i < job.getTriggerContentUris().length; i++) {
- JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
- pw.print(prefix); pw.print(" ");
- pw.print(Integer.toHexString(trig.getFlags()));
- pw.print(' '); pw.println(trig.getUri());
- }
- if (job.getTriggerContentUpdateDelay() >= 0) {
- pw.print(prefix); pw.print(" Trigger update delay: ");
- TimeUtils.formatDuration(job.getTriggerContentUpdateDelay(), pw);
- pw.println();
- }
- if (job.getTriggerContentMaxDelay() >= 0) {
- pw.print(prefix); pw.print(" Trigger max delay: ");
- TimeUtils.formatDuration(job.getTriggerContentMaxDelay(), pw);
- pw.println();
- }
- }
- if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
- pw.print(prefix); pw.print(" Extras: ");
- pw.println(job.getExtras().toShortString());
- }
- if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
- pw.print(prefix); pw.print(" Transient extras: ");
- pw.println(job.getTransientExtras().toShortString());
- }
- if (job.getClipData() != null) {
- pw.print(prefix); pw.print(" Clip data: ");
- StringBuilder b = new StringBuilder(128);
- job.getClipData().toShortString(b);
- pw.println(b);
- }
- if (uriPerms != null) {
- pw.print(prefix); pw.println(" Granted URI permissions:");
- uriPerms.dump(pw, prefix + " ");
- }
- if (job.getRequiredNetwork() != null) {
- pw.print(prefix); pw.print(" Network type: ");
- pw.println(job.getRequiredNetwork());
- }
- if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- pw.print(prefix); pw.print(" Network download bytes: ");
- pw.println(mTotalNetworkDownloadBytes);
- }
- if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- pw.print(prefix); pw.print(" Network upload bytes: ");
- pw.println(mTotalNetworkUploadBytes);
- }
- if (job.getMinLatencyMillis() != 0) {
- pw.print(prefix); pw.print(" Minimum latency: ");
- TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
- pw.println();
- }
- if (job.getMaxExecutionDelayMillis() != 0) {
- pw.print(prefix); pw.print(" Max execution delay: ");
- TimeUtils.formatDuration(job.getMaxExecutionDelayMillis(), pw);
- pw.println();
- }
- pw.print(prefix); pw.print(" Backoff: policy="); pw.print(job.getBackoffPolicy());
- pw.print(" initial="); TimeUtils.formatDuration(job.getInitialBackoffMillis(), pw);
- pw.println();
- if (job.hasEarlyConstraint()) {
- pw.print(prefix); pw.println(" Has early constraint");
- }
- if (job.hasLateConstraint()) {
- pw.print(prefix); pw.println(" Has late constraint");
- }
- }
- pw.print(prefix); pw.print("Required constraints:");
- dumpConstraints(pw, requiredConstraints);
- pw.println();
- if (full) {
- pw.print(prefix); pw.print("Satisfied constraints:");
- dumpConstraints(pw, satisfiedConstraints);
- pw.println();
- pw.print(prefix); pw.print("Unsatisfied constraints:");
- dumpConstraints(pw,
- ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
- pw.println();
- if (dozeWhitelisted) {
- pw.print(prefix); pw.println("Doze whitelisted: true");
- }
- if (uidActive) {
- pw.print(prefix); pw.println("Uid: active");
- }
- if (job.isExemptedFromAppStandby()) {
- pw.print(prefix); pw.println("Is exempted from app standby");
- }
- }
- if (trackingControllers != 0) {
- pw.print(prefix); pw.print("Tracking:");
- if ((trackingControllers&TRACKING_BATTERY) != 0) pw.print(" BATTERY");
- if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) pw.print(" CONNECTIVITY");
- if ((trackingControllers&TRACKING_CONTENT) != 0) pw.print(" CONTENT");
- if ((trackingControllers&TRACKING_IDLE) != 0) pw.print(" IDLE");
- if ((trackingControllers&TRACKING_STORAGE) != 0) pw.print(" STORAGE");
- if ((trackingControllers&TRACKING_TIME) != 0) pw.print(" TIME");
- if ((trackingControllers & TRACKING_QUOTA) != 0) pw.print(" QUOTA");
- pw.println();
- }
-
- pw.print(prefix); pw.println("Implicit constraints:");
- pw.print(prefix); pw.print(" readyNotDozing: ");
- pw.println(mReadyNotDozing);
- pw.print(prefix); pw.print(" readyNotRestrictedInBg: ");
- pw.println(mReadyNotRestrictedInBg);
- if (!job.isPeriodic() && hasDeadlineConstraint()) {
- pw.print(prefix); pw.print(" readyDeadlineSatisfied: ");
- pw.println(mReadyDeadlineSatisfied);
- }
-
- if (changedAuthorities != null) {
- pw.print(prefix); pw.println("Changed authorities:");
- for (int i=0; i<changedAuthorities.size(); i++) {
- pw.print(prefix); pw.print(" "); pw.println(changedAuthorities.valueAt(i));
- }
- if (changedUris != null) {
- pw.print(prefix); pw.println("Changed URIs:");
- for (int i=0; i<changedUris.size(); i++) {
- pw.print(prefix); pw.print(" "); pw.println(changedUris.valueAt(i));
- }
- }
- }
- if (network != null) {
- pw.print(prefix); pw.print("Network: "); pw.println(network);
- }
- if (pendingWork != null && pendingWork.size() > 0) {
- pw.print(prefix); pw.println("Pending work:");
- for (int i = 0; i < pendingWork.size(); i++) {
- dumpJobWorkItem(pw, prefix, pendingWork.get(i), i);
- }
- }
- if (executingWork != null && executingWork.size() > 0) {
- pw.print(prefix); pw.println("Executing work:");
- for (int i = 0; i < executingWork.size(); i++) {
- dumpJobWorkItem(pw, prefix, executingWork.get(i), i);
- }
- }
- pw.print(prefix); pw.print("Standby bucket: ");
- pw.println(getBucketName());
- if (standbyBucket > 0) {
- pw.print(prefix); pw.print("Base heartbeat: ");
- pw.println(baseHeartbeat);
- }
- if (whenStandbyDeferred != 0) {
- pw.print(prefix); pw.print(" Deferred since: ");
- TimeUtils.formatDuration(whenStandbyDeferred, elapsedRealtimeMillis, pw);
- pw.println();
- }
- if (mFirstForceBatchedTimeElapsed != 0) {
- pw.print(prefix);
- pw.print(" Time since first force batch attempt: ");
- TimeUtils.formatDuration(mFirstForceBatchedTimeElapsed, elapsedRealtimeMillis, pw);
- pw.println();
- }
- pw.print(prefix); pw.print("Enqueue time: ");
- TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw);
- pw.println();
- pw.print(prefix); pw.print("Run time: earliest=");
- formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis);
- pw.print(", latest=");
- formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis);
- pw.print(", original latest=");
- formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis,
- NO_LATEST_RUNTIME, elapsedRealtimeMillis);
- pw.println();
- if (numFailures != 0) {
- pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures);
- }
- if (mLastSuccessfulRunTime != 0) {
- pw.print(prefix); pw.print("Last successful run: ");
- pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastSuccessfulRunTime));
- }
- if (mLastFailedRunTime != 0) {
- pw.print(prefix); pw.print("Last failed run: ");
- pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastFailedRunTime));
- }
- }
-
- public void dump(ProtoOutputStream proto, long fieldId, boolean full, long elapsedRealtimeMillis) {
- final long token = proto.start(fieldId);
-
- proto.write(JobStatusDumpProto.CALLING_UID, callingUid);
- proto.write(JobStatusDumpProto.TAG, tag);
- proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid());
- proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId());
- proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName());
- proto.write(JobStatusDumpProto.INTERNAL_FLAGS, getInternalFlags());
-
- if (full) {
- final long jiToken = proto.start(JobStatusDumpProto.JOB_INFO);
-
- job.getService().writeToProto(proto, JobStatusDumpProto.JobInfo.SERVICE);
-
- proto.write(JobStatusDumpProto.JobInfo.IS_PERIODIC, job.isPeriodic());
- proto.write(JobStatusDumpProto.JobInfo.PERIOD_INTERVAL_MS, job.getIntervalMillis());
- proto.write(JobStatusDumpProto.JobInfo.PERIOD_FLEX_MS, job.getFlexMillis());
-
- proto.write(JobStatusDumpProto.JobInfo.IS_PERSISTED, job.isPersisted());
- proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getPriority());
- proto.write(JobStatusDumpProto.JobInfo.FLAGS, job.getFlags());
-
- proto.write(JobStatusDumpProto.JobInfo.REQUIRES_CHARGING, job.isRequireCharging());
- proto.write(JobStatusDumpProto.JobInfo.REQUIRES_BATTERY_NOT_LOW, job.isRequireBatteryNotLow());
- proto.write(JobStatusDumpProto.JobInfo.REQUIRES_DEVICE_IDLE, job.isRequireDeviceIdle());
-
- if (job.getTriggerContentUris() != null) {
- for (int i = 0; i < job.getTriggerContentUris().length; i++) {
- final long tcuToken = proto.start(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_URIS);
- JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
-
- proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.FLAGS, trig.getFlags());
- Uri u = trig.getUri();
- if (u != null) {
- proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.URI, u.toString());
- }
-
- proto.end(tcuToken);
- }
- if (job.getTriggerContentUpdateDelay() >= 0) {
- proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_UPDATE_DELAY_MS,
- job.getTriggerContentUpdateDelay());
- }
- if (job.getTriggerContentMaxDelay() >= 0) {
- proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_MAX_DELAY_MS,
- job.getTriggerContentMaxDelay());
- }
- }
- if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
- job.getExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.EXTRAS);
- }
- if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
- job.getTransientExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS);
- }
- if (job.getClipData() != null) {
- job.getClipData().writeToProto(proto, JobStatusDumpProto.JobInfo.CLIP_DATA);
- }
- if (uriPerms != null) {
- uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS);
- }
- if (job.getRequiredNetwork() != null) {
- job.getRequiredNetwork().writeToProto(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK);
- }
- if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_DOWNLOAD_BYTES,
- mTotalNetworkDownloadBytes);
- }
- if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_UPLOAD_BYTES,
- mTotalNetworkUploadBytes);
- }
- proto.write(JobStatusDumpProto.JobInfo.MIN_LATENCY_MS, job.getMinLatencyMillis());
- proto.write(JobStatusDumpProto.JobInfo.MAX_EXECUTION_DELAY_MS, job.getMaxExecutionDelayMillis());
-
- final long bpToken = proto.start(JobStatusDumpProto.JobInfo.BACKOFF_POLICY);
- proto.write(JobStatusDumpProto.JobInfo.Backoff.POLICY, job.getBackoffPolicy());
- proto.write(JobStatusDumpProto.JobInfo.Backoff.INITIAL_BACKOFF_MS,
- job.getInitialBackoffMillis());
- proto.end(bpToken);
-
- proto.write(JobStatusDumpProto.JobInfo.HAS_EARLY_CONSTRAINT, job.hasEarlyConstraint());
- proto.write(JobStatusDumpProto.JobInfo.HAS_LATE_CONSTRAINT, job.hasLateConstraint());
-
- proto.end(jiToken);
- }
-
- dumpConstraints(proto, JobStatusDumpProto.REQUIRED_CONSTRAINTS, requiredConstraints);
- if (full) {
- dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
- dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
- ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
- proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, dozeWhitelisted);
- proto.write(JobStatusDumpProto.IS_UID_ACTIVE, uidActive);
- proto.write(JobStatusDumpProto.IS_EXEMPTED_FROM_APP_STANDBY,
- job.isExemptedFromAppStandby());
- }
-
- // Tracking controllers
- if ((trackingControllers&TRACKING_BATTERY) != 0) {
- proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
- JobStatusDumpProto.TRACKING_BATTERY);
- }
- if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) {
- proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
- JobStatusDumpProto.TRACKING_CONNECTIVITY);
- }
- if ((trackingControllers&TRACKING_CONTENT) != 0) {
- proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
- JobStatusDumpProto.TRACKING_CONTENT);
- }
- if ((trackingControllers&TRACKING_IDLE) != 0) {
- proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
- JobStatusDumpProto.TRACKING_IDLE);
- }
- if ((trackingControllers&TRACKING_STORAGE) != 0) {
- proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
- JobStatusDumpProto.TRACKING_STORAGE);
- }
- if ((trackingControllers&TRACKING_TIME) != 0) {
- proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
- JobStatusDumpProto.TRACKING_TIME);
- }
- if ((trackingControllers & TRACKING_QUOTA) != 0) {
- proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
- JobStatusDumpProto.TRACKING_QUOTA);
- }
-
- // Implicit constraints
- final long icToken = proto.start(JobStatusDumpProto.IMPLICIT_CONSTRAINTS);
- proto.write(JobStatusDumpProto.ImplicitConstraints.IS_NOT_DOZING, mReadyNotDozing);
- proto.write(JobStatusDumpProto.ImplicitConstraints.IS_NOT_RESTRICTED_IN_BG,
- mReadyNotRestrictedInBg);
- proto.end(icToken);
-
- if (changedAuthorities != null) {
- for (int k = 0; k < changedAuthorities.size(); k++) {
- proto.write(JobStatusDumpProto.CHANGED_AUTHORITIES, changedAuthorities.valueAt(k));
- }
- }
- if (changedUris != null) {
- for (int i = 0; i < changedUris.size(); i++) {
- Uri u = changedUris.valueAt(i);
- proto.write(JobStatusDumpProto.CHANGED_URIS, u.toString());
- }
- }
-
- if (network != null) {
- network.writeToProto(proto, JobStatusDumpProto.NETWORK);
- }
-
- if (pendingWork != null && pendingWork.size() > 0) {
- for (int i = 0; i < pendingWork.size(); i++) {
- dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i));
- }
- }
- if (executingWork != null && executingWork.size() > 0) {
- for (int i = 0; i < executingWork.size(); i++) {
- dumpJobWorkItem(proto, JobStatusDumpProto.EXECUTING_WORK, executingWork.get(i));
- }
- }
-
- proto.write(JobStatusDumpProto.STANDBY_BUCKET, standbyBucket);
- proto.write(JobStatusDumpProto.ENQUEUE_DURATION_MS, elapsedRealtimeMillis - enqueueTime);
- proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_DEFERRAL_MS,
- whenStandbyDeferred == 0 ? 0 : elapsedRealtimeMillis - whenStandbyDeferred);
- proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_FORCE_BATCH_ATTEMPT_MS,
- mFirstForceBatchedTimeElapsed == 0
- ? 0 : elapsedRealtimeMillis - mFirstForceBatchedTimeElapsed);
- if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
- proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS, 0);
- } else {
- proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS,
- earliestRunTimeElapsedMillis - elapsedRealtimeMillis);
- }
- if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
- proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS, 0);
- } else {
- proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS,
- latestRunTimeElapsedMillis - elapsedRealtimeMillis);
- }
-
- proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures);
- proto.write(JobStatusDumpProto.LAST_SUCCESSFUL_RUN_TIME, mLastSuccessfulRunTime);
- proto.write(JobStatusDumpProto.LAST_FAILED_RUN_TIME, mLastFailedRunTime);
-
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
deleted file mode 100644
index b8cfac4..0000000
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ /dev/null
@@ -1,2829 +0,0 @@
-/*
- * Copyright (C) 2018 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 static android.text.format.DateUtils.HOUR_IN_MILLIS;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-
-import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
-import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
-import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
-import static com.android.server.job.JobSchedulerService.RARE_INDEX;
-import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.AlarmManager;
-import android.app.AppGlobals;
-import android.app.IUidObserver;
-import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.KeyValueListParser;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseSetArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.job.ConstantsProto;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobServiceContext;
-import com.android.server.job.StateControllerProto;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Controller that tracks whether an app has exceeded its standby bucket quota.
- *
- * With initial defaults, each app in each bucket is given 10 minutes to run within its respective
- * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a
- * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run
- * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some
- * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new
- * quota is immediately applied to it.
- *
- * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on
- * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
- * not be allowed to run more than 20 jobs within the past 10 minutes.
- *
- * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
- * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state do not count
- * towards any quota and are not restricted regardless of the app's state change.
- *
- * Jobs will not be throttled when the device is charging. The device is considered to be charging
- * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
- *
- * Note: all limits are enforced per bucket window unless explicitly stated otherwise.
- * All stated values are configurable and subject to change. See {@link QcConstants} for current
- * defaults.
- *
- * Test: atest com.android.server.job.controllers.QuotaControllerTest
- */
-public final class QuotaController extends StateController {
- private static final String TAG = "JobScheduler.Quota";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
- private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
-
- /**
- * A sparse array of ArrayMaps, which is suitable for holding (userId, packageName)->object
- * associations.
- */
- private static class UserPackageMap<T> {
- private final SparseArray<ArrayMap<String, T>> mData = new SparseArray<>();
-
- public void add(int userId, @NonNull String packageName, @Nullable T obj) {
- ArrayMap<String, T> data = mData.get(userId);
- if (data == null) {
- data = new ArrayMap<String, T>();
- mData.put(userId, data);
- }
- data.put(packageName, obj);
- }
-
- public void clear() {
- for (int i = 0; i < mData.size(); ++i) {
- mData.valueAt(i).clear();
- }
- }
-
- /** Removes all the data for the user, if there was any. */
- public void delete(int userId) {
- mData.delete(userId);
- }
-
- /** Removes the data for the user and package, if there was any. */
- public void delete(int userId, @NonNull String packageName) {
- ArrayMap<String, T> data = mData.get(userId);
- if (data != null) {
- data.remove(packageName);
- }
- }
-
- @Nullable
- public T get(int userId, @NonNull String packageName) {
- ArrayMap<String, T> data = mData.get(userId);
- if (data != null) {
- return data.get(packageName);
- }
- return null;
- }
-
- /** @see SparseArray#indexOfKey */
- public int indexOfKey(int userId) {
- return mData.indexOfKey(userId);
- }
-
- /** Returns the userId at the given index. */
- public int keyAt(int index) {
- return mData.keyAt(index);
- }
-
- /** Returns the package name at the given index. */
- @NonNull
- public String keyAt(int userIndex, int packageIndex) {
- return mData.valueAt(userIndex).keyAt(packageIndex);
- }
-
- /** Returns the size of the outer (userId) array. */
- public int numUsers() {
- return mData.size();
- }
-
- public int numPackagesForUser(int userId) {
- ArrayMap<String, T> data = mData.get(userId);
- return data == null ? 0 : data.size();
- }
-
- /** Returns the value T at the given user and index. */
- @Nullable
- public T valueAt(int userIndex, int packageIndex) {
- return mData.valueAt(userIndex).valueAt(packageIndex);
- }
-
- public void forEach(Consumer<T> consumer) {
- for (int i = numUsers() - 1; i >= 0; --i) {
- ArrayMap<String, T> data = mData.valueAt(i);
- for (int j = data.size() - 1; j >= 0; --j) {
- consumer.accept(data.valueAt(j));
- }
- }
- }
- }
-
- /**
- * Standardize the output of userId-packageName combo.
- */
- private static String string(int userId, String packageName) {
- return "<" + userId + ">" + packageName;
- }
-
- private static final class Package {
- public final String packageName;
- public final int userId;
-
- Package(int userId, String packageName) {
- this.userId = userId;
- this.packageName = packageName;
- }
-
- @Override
- public String toString() {
- return string(userId, packageName);
- }
-
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId);
- proto.write(StateControllerProto.QuotaController.Package.NAME, packageName);
-
- proto.end(token);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Package) {
- Package other = (Package) obj;
- return userId == other.userId && Objects.equals(packageName, other.packageName);
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- return packageName.hashCode() + userId;
- }
- }
-
- private static int hashLong(long val) {
- return (int) (val ^ (val >>> 32));
- }
-
- @VisibleForTesting
- static class ExecutionStats {
- /**
- * The time after which this record should be considered invalid (out of date), in the
- * elapsed realtime timebase.
- */
- public long expirationTimeElapsed;
-
- public long windowSizeMs;
- public int jobCountLimit;
- public int sessionCountLimit;
-
- /** The total amount of time the app ran in its respective bucket window size. */
- public long executionTimeInWindowMs;
- public int bgJobCountInWindow;
-
- /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */
- public long executionTimeInMaxPeriodMs;
- public int bgJobCountInMaxPeriod;
-
- /**
- * The number of {@link TimingSession}s within the bucket window size. This will include
- * sessions that started before the window as long as they end within the window.
- */
- public int sessionCountInWindow;
-
- /**
- * The time after which the app will be under the bucket quota and can start running jobs
- * again. This is only valid if
- * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs},
- * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
- * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
- * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
- */
- public long inQuotaTimeElapsed;
-
- /**
- * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
- * in the elapsed realtime timebase.
- */
- public long jobRateLimitExpirationTimeElapsed;
-
- /**
- * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}.
- * It may contain a few stale entries since cleanup won't happen exactly every
- * {@link #mRateLimitingWindowMs}.
- */
- public int jobCountInRateLimitingWindow;
-
- /**
- * The time after which {@link #sessionCountInRateLimitingWindow} should be considered
- * invalid, in the elapsed realtime timebase.
- */
- public long sessionRateLimitExpirationTimeElapsed;
-
- /**
- * The number of {@link TimingSession}s that ran in at least the last
- * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't
- * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered
- * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}.
- */
- public int sessionCountInRateLimitingWindow;
-
- @Override
- public String toString() {
- return "expirationTime=" + expirationTimeElapsed + ", "
- + "windowSizeMs=" + windowSizeMs + ", "
- + "jobCountLimit=" + jobCountLimit + ", "
- + "sessionCountLimit=" + sessionCountLimit + ", "
- + "executionTimeInWindow=" + executionTimeInWindowMs + ", "
- + "bgJobCountInWindow=" + bgJobCountInWindow + ", "
- + "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", "
- + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
- + "sessionCountInWindow=" + sessionCountInWindow + ", "
- + "inQuotaTime=" + inQuotaTimeElapsed + ", "
- + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
- + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", "
- + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", "
- + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof ExecutionStats) {
- ExecutionStats other = (ExecutionStats) obj;
- return this.expirationTimeElapsed == other.expirationTimeElapsed
- && this.windowSizeMs == other.windowSizeMs
- && this.jobCountLimit == other.jobCountLimit
- && this.sessionCountLimit == other.sessionCountLimit
- && this.executionTimeInWindowMs == other.executionTimeInWindowMs
- && this.bgJobCountInWindow == other.bgJobCountInWindow
- && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
- && this.sessionCountInWindow == other.sessionCountInWindow
- && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
- && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed
- && this.jobRateLimitExpirationTimeElapsed
- == other.jobRateLimitExpirationTimeElapsed
- && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow
- && this.sessionRateLimitExpirationTimeElapsed
- == other.sessionRateLimitExpirationTimeElapsed
- && this.sessionCountInRateLimitingWindow
- == other.sessionCountInRateLimitingWindow;
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- int result = 0;
- result = 31 * result + hashLong(expirationTimeElapsed);
- result = 31 * result + hashLong(windowSizeMs);
- result = 31 * result + hashLong(jobCountLimit);
- result = 31 * result + hashLong(sessionCountLimit);
- result = 31 * result + hashLong(executionTimeInWindowMs);
- result = 31 * result + bgJobCountInWindow;
- result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
- result = 31 * result + bgJobCountInMaxPeriod;
- result = 31 * result + sessionCountInWindow;
- result = 31 * result + hashLong(inQuotaTimeElapsed);
- result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed);
- result = 31 * result + jobCountInRateLimitingWindow;
- result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed);
- result = 31 * result + sessionCountInRateLimitingWindow;
- return result;
- }
- }
-
- /** List of all tracked jobs keyed by source package-userId combo. */
- private final UserPackageMap<ArraySet<JobStatus>> mTrackedJobs = new UserPackageMap<>();
-
- /** Timer for each package-userId combo. */
- private final UserPackageMap<Timer> mPkgTimers = new UserPackageMap<>();
-
- /** List of all timing sessions for a package-userId combo, in chronological order. */
- private final UserPackageMap<List<TimingSession>> mTimingSessions = new UserPackageMap<>();
-
- /**
- * List of alarm listeners for each package that listen for when each package comes back within
- * quota.
- */
- private final UserPackageMap<QcAlarmListener> mInQuotaAlarmListeners = new UserPackageMap<>();
-
- /** Cached calculation results for each app, with the standby buckets as the array indices. */
- private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
-
- /** List of UIDs currently in the foreground. */
- private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
-
- /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
- private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>();
-
- /**
- * List of jobs that started while the UID was in the TOP state. There will be no more than
- * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
- * fine.
- */
- private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
-
- private final ActivityManagerInternal mActivityManagerInternal;
- private final AlarmManager mAlarmManager;
- private final ChargingTracker mChargeTracker;
- private final Handler mHandler;
- private final QcConstants mQcConstants;
-
- private volatile boolean mInParole;
-
- /**
- * If the QuotaController should throttle apps based on their standby bucket and job activity.
- * If false, all jobs will have their CONSTRAINT_WITHIN_QUOTA bit set to true immediately and
- * indefinitely.
- */
- private boolean mShouldThrottle;
-
- /** How much time each app will have to run jobs within their standby bucket window. */
- private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
-
- /**
- * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
- * window.
- */
- private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS;
-
- /**
- * How much time the app should have before transitioning from out-of-quota to in-quota.
- * This should not affect processing if the app is already in-quota.
- */
- private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
-
- /**
- * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
- * when an app will have enough quota to transition from out-of-quota to in-quota.
- */
- private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
-
- /**
- * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
- * app will have enough quota to transition from out-of-quota to in-quota.
- */
- private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
-
- /** The period of time used to rate limit recently run jobs. */
- private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
-
- /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */
- private int mMaxJobCountPerRateLimitingWindow =
- QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
-
- /**
- * The maximum number of {@link TimingSession}s that can run within the past {@link
- * #mRateLimitingWindowMs}.
- */
- private int mMaxSessionCountPerRateLimitingWindow =
- QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
-
- private long mNextCleanupTimeElapsed = 0;
- private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
- new AlarmManager.OnAlarmListener() {
- @Override
- public void onAlarm() {
- mHandler.obtainMessage(MSG_CLEAN_UP_SESSIONS).sendToTarget();
- }
- };
-
- private final IUidObserver mUidObserver = new IUidObserver.Stub() {
- @Override
- public void onUidStateChanged(int uid, int procState, long procStateSeq) {
- mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
- }
-
- @Override
- public void onUidGone(int uid, boolean disabled) {
- }
-
- @Override
- public void onUidActive(int uid) {
- }
-
- @Override
- public void onUidIdle(int uid, boolean disabled) {
- }
-
- @Override
- public void onUidCachedChanged(int uid, boolean cached) {
- }
- };
-
- private final BroadcastReceiver mPackageAddedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent == null) {
- return;
- }
- if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- return;
- }
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
- synchronized (mLock) {
- mUidToPackageCache.remove(uid);
- }
- }
- };
-
- /**
- * The rolling window size for each standby bucket. Within each window, an app will have 10
- * minutes to run its jobs.
- */
- private final long[] mBucketPeriodsMs = new long[]{
- QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
- };
-
- /** The maximum period any bucket can have. */
- private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
-
- /**
- * The maximum number of jobs based on its standby bucket. For each max value count in the
- * array, the app will not be allowed to run more than that many number of jobs within the
- * latest time interval of its rolling window size.
- *
- * @see #mBucketPeriodsMs
- */
- private final int[] mMaxBucketJobCounts = new int[]{
- QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
- QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
- QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
- QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
- };
-
- /**
- * The maximum number of {@link TimingSession}s based on its standby bucket. For each max value
- * count in the array, the app will not be allowed to have more than that many number of
- * {@link TimingSession}s within the latest time interval of its rolling window size.
- *
- * @see #mBucketPeriodsMs
- */
- private final int[] mMaxBucketSessionCounts = new int[]{
- QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE,
- QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING,
- QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT,
- QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
- };
-
- /**
- * Treat two distinct {@link TimingSession}s as the same if they start and end within this
- * amount of time of each other.
- */
- private long mTimingSessionCoalescingDurationMs =
- QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
-
- /** An app has reached its quota. The message should contain a {@link Package} object. */
- private static final int MSG_REACHED_QUOTA = 0;
- /** Drop any old timing sessions. */
- private static final int MSG_CLEAN_UP_SESSIONS = 1;
- /** Check if a package is now within its quota. */
- private static final int MSG_CHECK_PACKAGE = 2;
- /** Process state for a UID has changed. */
- private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
-
- public QuotaController(JobSchedulerService service) {
- super(service);
- mHandler = new QcHandler(mContext.getMainLooper());
- mChargeTracker = new ChargingTracker();
- mChargeTracker.startTracking();
- mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- mQcConstants = new QcConstants(mHandler);
-
- final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- mContext.registerReceiverAsUser(mPackageAddedReceiver, UserHandle.ALL, filter, null, null);
-
- // Set up the app standby bucketing tracker
- UsageStatsManagerInternal usageStats = LocalServices.getService(
- UsageStatsManagerInternal.class);
- usageStats.addAppIdleStateChangeListener(new StandbyTracker());
-
- try {
- ActivityManager.getService().registerUidObserver(mUidObserver,
- ActivityManager.UID_OBSERVER_PROCSTATE,
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
- } catch (RemoteException e) {
- // ignored; both services live in system_server
- }
-
- mShouldThrottle = !mConstants.USE_HEARTBEATS;
- }
-
- @Override
- public void onSystemServicesReady() {
- mQcConstants.start(mContext.getContentResolver());
- }
-
- @Override
- public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- final int userId = jobStatus.getSourceUserId();
- final String pkgName = jobStatus.getSourcePackageName();
- // Still need to track jobs even if mShouldThrottle is false in case it's set to true at
- // some point.
- ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
- if (jobs == null) {
- jobs = new ArraySet<>();
- mTrackedJobs.add(userId, pkgName, jobs);
- }
- jobs.add(jobStatus);
- jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA);
- if (mShouldThrottle) {
- final boolean isWithinQuota = isWithinQuotaLocked(jobStatus);
- setConstraintSatisfied(jobStatus, isWithinQuota);
- if (!isWithinQuota) {
- maybeScheduleStartAlarmLocked(userId, pkgName,
- getEffectiveStandbyBucket(jobStatus));
- }
- } else {
- // QuotaController isn't throttling, so always set to true.
- jobStatus.setQuotaConstraintSatisfied(true);
- }
- }
-
- @Override
- public void prepareForExecutionLocked(JobStatus jobStatus) {
- if (DEBUG) {
- Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
- }
-
- final int uid = jobStatus.getSourceUid();
- if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
- if (DEBUG) {
- Slog.d(TAG, jobStatus.toShortString() + " is top started job");
- }
- mTopStartedJobs.add(jobStatus);
- // Top jobs won't count towards quota so there's no need to involve the Timer.
- return;
- }
-
- final int userId = jobStatus.getSourceUserId();
- final String packageName = jobStatus.getSourcePackageName();
- Timer timer = mPkgTimers.get(userId, packageName);
- if (timer == null) {
- timer = new Timer(uid, userId, packageName);
- mPkgTimers.add(userId, packageName, timer);
- }
- timer.startTrackingJobLocked(jobStatus);
- }
-
- @Override
- public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
- boolean forUpdate) {
- if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
- Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
- if (timer != null) {
- timer.stopTrackingJob(jobStatus);
- }
- ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
- if (jobs != null) {
- jobs.remove(jobStatus);
- }
- mTopStartedJobs.remove(jobStatus);
- }
- }
-
- @Override
- public void onConstantsUpdatedLocked() {
- if (mShouldThrottle == mConstants.USE_HEARTBEATS) {
- mShouldThrottle = !mConstants.USE_HEARTBEATS;
-
- // Update job bookkeeping out of band.
- BackgroundThread.getHandler().post(() -> {
- synchronized (mLock) {
- maybeUpdateAllConstraintsLocked();
- }
- });
- }
- }
-
- @Override
- public void onAppRemovedLocked(String packageName, int uid) {
- if (packageName == null) {
- Slog.wtf(TAG, "Told app removed but given null package name.");
- return;
- }
- final int userId = UserHandle.getUserId(uid);
- mTrackedJobs.delete(userId, packageName);
- Timer timer = mPkgTimers.get(userId, packageName);
- if (timer != null) {
- if (timer.isActive()) {
- Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off.");
- timer.dropEverythingLocked();
- }
- mPkgTimers.delete(userId, packageName);
- }
- mTimingSessions.delete(userId, packageName);
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null) {
- mAlarmManager.cancel(alarmListener);
- mInQuotaAlarmListeners.delete(userId, packageName);
- }
- mExecutionStatsCache.delete(userId, packageName);
- mForegroundUids.delete(uid);
- mUidToPackageCache.remove(uid);
- }
-
- @Override
- public void onUserRemovedLocked(int userId) {
- mTrackedJobs.delete(userId);
- mPkgTimers.delete(userId);
- mTimingSessions.delete(userId);
- mInQuotaAlarmListeners.delete(userId);
- mExecutionStatsCache.delete(userId);
- mUidToPackageCache.clear();
- }
-
- private boolean isUidInForeground(int uid) {
- if (UserHandle.isCore(uid)) {
- return true;
- }
- synchronized (mLock) {
- return mForegroundUids.get(uid);
- }
- }
-
- /** @return true if the job was started while the app was in the TOP state. */
- private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
- return mTopStartedJobs.contains(jobStatus);
- }
-
- /** Returns the maximum amount of time this job could run for. */
- public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
- // If quota is currently "free", then the job can run for the full amount of time.
- if (mChargeTracker.isCharging()
- || mInParole
- || isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())) {
- return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
- }
- return getRemainingExecutionTimeLocked(jobStatus);
- }
-
- /**
- * Returns an appropriate standby bucket for the job, taking into account any standby
- * exemptions.
- */
- private int getEffectiveStandbyBucket(@NonNull final JobStatus jobStatus) {
- if (jobStatus.uidActive || jobStatus.getJob().isExemptedFromAppStandby()) {
- // Treat these cases as if they're in the ACTIVE bucket so that they get throttled
- // like other ACTIVE apps.
- return ACTIVE_INDEX;
- }
- return jobStatus.getStandbyBucket();
- }
-
- @VisibleForTesting
- boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
- final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
- // A job is within quota if one of the following is true:
- // 1. it was started while the app was in the TOP state
- // 2. the app is currently in the foreground
- // 3. the app overall is within its quota
- return isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())
- || isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
- }
-
- @VisibleForTesting
- boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
- final int standbyBucket) {
- if (standbyBucket == NEVER_INDEX) return false;
- // This check is needed in case the flag is toggled after a job has been registered.
- if (!mShouldThrottle) return true;
-
- // Quota constraint is not enforced while charging or when parole is on.
- if (mChargeTracker.isCharging() || mInParole) {
- return true;
- }
-
- ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- return getRemainingExecutionTimeLocked(stats) > 0
- && isUnderJobCountQuotaLocked(stats, standbyBucket)
- && isUnderSessionCountQuotaLocked(stats, standbyBucket);
- }
-
- private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
- final long now = sElapsedRealtimeClock.millis();
- final boolean isUnderAllowedTimeQuota =
- (stats.jobRateLimitExpirationTimeElapsed <= now
- || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
- return isUnderAllowedTimeQuota
- && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
- }
-
- private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
- final long now = sElapsedRealtimeClock.millis();
- final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
- || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
- return isUnderAllowedTimeQuota
- && stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket];
- }
-
- @VisibleForTesting
- long getRemainingExecutionTimeLocked(@NonNull final JobStatus jobStatus) {
- return getRemainingExecutionTimeLocked(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName(),
- getEffectiveStandbyBucket(jobStatus));
- }
-
- @VisibleForTesting
- long getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName) {
- final int standbyBucket = JobSchedulerService.standbyBucketForPackage(packageName,
- userId, sElapsedRealtimeClock.millis());
- return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket);
- }
-
- /**
- * Returns the amount of time, in milliseconds, that this job has remaining to run based on its
- * current standby bucket. Time remaining could be negative if the app was moved from a less
- * restricted to a more restricted bucket.
- */
- private long getRemainingExecutionTimeLocked(final int userId,
- @NonNull final String packageName, final int standbyBucket) {
- if (standbyBucket == NEVER_INDEX) {
- return 0;
- }
- return getRemainingExecutionTimeLocked(
- getExecutionStatsLocked(userId, packageName, standbyBucket));
- }
-
- private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) {
- return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs,
- mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs);
- }
-
- /**
- * Returns the amount of time, in milliseconds, until the package would have reached its
- * duration quota, assuming it has a job counting towards its quota the entire time. This takes
- * into account any {@link TimingSession}s that may roll out of the window as the job is
- * running.
- */
- @VisibleForTesting
- long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) {
- final long nowElapsed = sElapsedRealtimeClock.millis();
- final int standbyBucket = JobSchedulerService.standbyBucketForPackage(
- packageName, userId, nowElapsed);
- if (standbyBucket == NEVER_INDEX) {
- return 0;
- }
- List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
- if (sessions == null || sessions.size() == 0) {
- return mAllowedTimePerPeriodMs;
- }
-
- final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
- final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
- final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs;
- final long maxExecutionTimeRemainingMs =
- mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
-
- // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
- // essentially run until they reach the maximum limit.
- if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
- return calculateTimeUntilQuotaConsumedLocked(
- sessions, startMaxElapsed, maxExecutionTimeRemainingMs);
- }
-
- // Need to check both max time and period time in case one is less than the other.
- // For example, max time remaining could be less than bucket time remaining, but sessions
- // contributing to the max time remaining could phase out enough that we'd want to use the
- // bucket value.
- return Math.min(
- calculateTimeUntilQuotaConsumedLocked(
- sessions, startMaxElapsed, maxExecutionTimeRemainingMs),
- calculateTimeUntilQuotaConsumedLocked(
- sessions, startWindowElapsed, allowedTimeRemainingMs));
- }
-
- /**
- * Calculates how much time it will take, in milliseconds, until the quota is fully consumed.
- *
- * @param windowStartElapsed The start of the window, in the elapsed realtime timebase.
- * @param deadSpaceMs How much time can be allowed to count towards the quota
- */
- private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions,
- final long windowStartElapsed, long deadSpaceMs) {
- long timeUntilQuotaConsumedMs = 0;
- long start = windowStartElapsed;
- for (int i = 0; i < sessions.size(); ++i) {
- TimingSession session = sessions.get(i);
-
- if (session.endTimeElapsed < windowStartElapsed) {
- // Outside of window. Ignore.
- continue;
- } else if (session.startTimeElapsed <= windowStartElapsed) {
- // Overlapping session. Can extend time by portion of session in window.
- timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed;
- start = session.endTimeElapsed;
- } else {
- // Completely within the window. Can only consider if there's enough dead space
- // to get to the start of the session.
- long diff = session.startTimeElapsed - start;
- if (diff > deadSpaceMs) {
- break;
- }
- timeUntilQuotaConsumedMs += diff
- + (session.endTimeElapsed - session.startTimeElapsed);
- deadSpaceMs -= diff;
- start = session.endTimeElapsed;
- }
- }
- // Will be non-zero if the loop didn't look at any sessions.
- timeUntilQuotaConsumedMs += deadSpaceMs;
- if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) {
- Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs);
- }
- return timeUntilQuotaConsumedMs;
- }
-
- /** Returns the execution stats of the app in the most recent window. */
- @VisibleForTesting
- @NonNull
- ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
- final int standbyBucket) {
- return getExecutionStatsLocked(userId, packageName, standbyBucket, true);
- }
-
- @NonNull
- private ExecutionStats getExecutionStatsLocked(final int userId,
- @NonNull final String packageName, final int standbyBucket,
- final boolean refreshStatsIfOld) {
- if (standbyBucket == NEVER_INDEX) {
- Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
- return new ExecutionStats();
- }
- ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
- if (appStats == null) {
- appStats = new ExecutionStats[mBucketPeriodsMs.length];
- mExecutionStatsCache.add(userId, packageName, appStats);
- }
- ExecutionStats stats = appStats[standbyBucket];
- if (stats == null) {
- stats = new ExecutionStats();
- appStats[standbyBucket] = stats;
- }
- if (refreshStatsIfOld) {
- final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
- final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
- final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
- Timer timer = mPkgTimers.get(userId, packageName);
- if ((timer != null && timer.isActive())
- || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
- || stats.windowSizeMs != bucketWindowSizeMs
- || stats.jobCountLimit != jobCountLimit
- || stats.sessionCountLimit != sessionCountLimit) {
- // The stats are no longer valid.
- stats.windowSizeMs = bucketWindowSizeMs;
- stats.jobCountLimit = jobCountLimit;
- stats.sessionCountLimit = sessionCountLimit;
- updateExecutionStatsLocked(userId, packageName, stats);
- }
- }
-
- return stats;
- }
-
- @VisibleForTesting
- void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
- @NonNull ExecutionStats stats) {
- stats.executionTimeInWindowMs = 0;
- stats.bgJobCountInWindow = 0;
- stats.executionTimeInMaxPeriodMs = 0;
- stats.bgJobCountInMaxPeriod = 0;
- stats.sessionCountInWindow = 0;
- stats.inQuotaTimeElapsed = 0;
-
- Timer timer = mPkgTimers.get(userId, packageName);
- final long nowElapsed = sElapsedRealtimeClock.millis();
- stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS;
- if (timer != null && timer.isActive()) {
- stats.executionTimeInWindowMs =
- stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
- stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
- // If the timer is active, the value will be stale at the next method call, so
- // invalidate now.
- stats.expirationTimeElapsed = nowElapsed;
- if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
- }
- if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
- }
- }
-
- List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
- if (sessions == null || sessions.size() == 0) {
- return;
- }
-
- final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
- final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
- int sessionCountInWindow = 0;
- // The minimum time between the start time and the beginning of the sessions that were
- // looked at --> how much time the stats will be valid for.
- long emptyTimeMs = Long.MAX_VALUE;
- // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
- // the most recent ones.
- final int loopStart = sessions.size() - 1;
- for (int i = loopStart; i >= 0; --i) {
- TimingSession session = sessions.get(i);
-
- // Window management.
- if (startWindowElapsed < session.endTimeElapsed) {
- final long start;
- if (startWindowElapsed < session.startTimeElapsed) {
- start = session.startTimeElapsed;
- emptyTimeMs =
- Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
- } else {
- // The session started before the window but ended within the window. Only
- // include the portion that was within the window.
- start = startWindowElapsed;
- emptyTimeMs = 0;
- }
-
- stats.executionTimeInWindowMs += session.endTimeElapsed - start;
- stats.bgJobCountInWindow += session.bgJobCount;
- if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
- + stats.windowSizeMs);
- }
- if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- session.endTimeElapsed + stats.windowSizeMs);
- }
- if (i == loopStart
- || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
- > mTimingSessionCoalescingDurationMs) {
- // Coalesce sessions if they are very close to each other in time
- sessionCountInWindow++;
-
- if (sessionCountInWindow >= stats.sessionCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- session.endTimeElapsed + stats.windowSizeMs);
- }
- }
- }
-
- // Max period check.
- if (startMaxElapsed < session.startTimeElapsed) {
- stats.executionTimeInMaxPeriodMs +=
- session.endTimeElapsed - session.startTimeElapsed;
- stats.bgJobCountInMaxPeriod += session.bgJobCount;
- emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
- if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
- - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
- }
- } else if (startMaxElapsed < session.endTimeElapsed) {
- // The session started before the window but ended within the window. Only include
- // the portion that was within the window.
- stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed;
- stats.bgJobCountInMaxPeriod += session.bgJobCount;
- emptyTimeMs = 0;
- if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- startMaxElapsed + stats.executionTimeInMaxPeriodMs
- - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
- }
- } else {
- // This session ended before the window. No point in going any further.
- break;
- }
- }
- stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
- stats.sessionCountInWindow = sessionCountInWindow;
- }
-
- /** Invalidate ExecutionStats for all apps. */
- @VisibleForTesting
- void invalidateAllExecutionStatsLocked() {
- final long nowElapsed = sElapsedRealtimeClock.millis();
- mExecutionStatsCache.forEach((appStats) -> {
- if (appStats != null) {
- for (int i = 0; i < appStats.length; ++i) {
- ExecutionStats stats = appStats[i];
- if (stats != null) {
- stats.expirationTimeElapsed = nowElapsed;
- }
- }
- }
- });
- }
-
- @VisibleForTesting
- void invalidateAllExecutionStatsLocked(final int userId,
- @NonNull final String packageName) {
- ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
- if (appStats != null) {
- final long nowElapsed = sElapsedRealtimeClock.millis();
- for (int i = 0; i < appStats.length; ++i) {
- ExecutionStats stats = appStats[i];
- if (stats != null) {
- stats.expirationTimeElapsed = nowElapsed;
- }
- }
- }
- }
-
- @VisibleForTesting
- void incrementJobCount(final int userId, @NonNull final String packageName, int count) {
- final long now = sElapsedRealtimeClock.millis();
- ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
- if (appStats == null) {
- appStats = new ExecutionStats[mBucketPeriodsMs.length];
- mExecutionStatsCache.add(userId, packageName, appStats);
- }
- for (int i = 0; i < appStats.length; ++i) {
- ExecutionStats stats = appStats[i];
- if (stats == null) {
- stats = new ExecutionStats();
- appStats[i] = stats;
- }
- if (stats.jobRateLimitExpirationTimeElapsed <= now) {
- stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
- stats.jobCountInRateLimitingWindow = 0;
- }
- stats.jobCountInRateLimitingWindow += count;
- }
- }
-
- private void incrementTimingSessionCount(final int userId, @NonNull final String packageName) {
- final long now = sElapsedRealtimeClock.millis();
- ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
- if (appStats == null) {
- appStats = new ExecutionStats[mBucketPeriodsMs.length];
- mExecutionStatsCache.add(userId, packageName, appStats);
- }
- for (int i = 0; i < appStats.length; ++i) {
- ExecutionStats stats = appStats[i];
- if (stats == null) {
- stats = new ExecutionStats();
- appStats[i] = stats;
- }
- if (stats.sessionRateLimitExpirationTimeElapsed <= now) {
- stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
- stats.sessionCountInRateLimitingWindow = 0;
- }
- stats.sessionCountInRateLimitingWindow++;
- }
- }
-
- @VisibleForTesting
- void saveTimingSession(final int userId, @NonNull final String packageName,
- @NonNull final TimingSession session) {
- synchronized (mLock) {
- List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
- if (sessions == null) {
- sessions = new ArrayList<>();
- mTimingSessions.add(userId, packageName, sessions);
- }
- sessions.add(session);
- // Adding a new session means that the current stats are now incorrect.
- invalidateAllExecutionStatsLocked(userId, packageName);
-
- maybeScheduleCleanupAlarmLocked();
- }
- }
-
- private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> {
- public long earliestEndElapsed = Long.MAX_VALUE;
-
- @Override
- public void accept(List<TimingSession> sessions) {
- if (sessions != null && sessions.size() > 0) {
- earliestEndElapsed = Math.min(earliestEndElapsed, sessions.get(0).endTimeElapsed);
- }
- }
-
- void reset() {
- earliestEndElapsed = Long.MAX_VALUE;
- }
- }
-
- private final EarliestEndTimeFunctor mEarliestEndTimeFunctor = new EarliestEndTimeFunctor();
-
- /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */
- @VisibleForTesting
- void maybeScheduleCleanupAlarmLocked() {
- if (mNextCleanupTimeElapsed > sElapsedRealtimeClock.millis()) {
- // There's already an alarm scheduled. Just stick with that one. There's no way we'll
- // end up scheduling an earlier alarm.
- if (DEBUG) {
- Slog.v(TAG, "Not scheduling cleanup since there's already one at "
- + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed
- - sElapsedRealtimeClock.millis()) + "ms)");
- }
- return;
- }
- mEarliestEndTimeFunctor.reset();
- mTimingSessions.forEach(mEarliestEndTimeFunctor);
- final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed;
- if (earliestEndElapsed == Long.MAX_VALUE) {
- // Couldn't find a good time to clean up. Maybe this was called after we deleted all
- // timing sessions.
- if (DEBUG) {
- Slog.d(TAG, "Didn't find a time to schedule cleanup");
- }
- return;
- }
- // Need to keep sessions for all apps up to the max period, regardless of their current
- // standby bucket.
- long nextCleanupElapsed = earliestEndElapsed + MAX_PERIOD_MS;
- if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) {
- // No need to clean up too often. Delay the alarm if the next cleanup would be too soon
- // after it.
- nextCleanupElapsed += 10 * MINUTE_IN_MILLIS;
- }
- mNextCleanupTimeElapsed = nextCleanupElapsed;
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
- mSessionCleanupAlarmListener, mHandler);
- if (DEBUG) {
- Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
- }
- }
-
- private void handleNewChargingStateLocked() {
- final long nowElapsed = sElapsedRealtimeClock.millis();
- final boolean isCharging = mChargeTracker.isCharging();
- if (DEBUG) {
- Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
- }
- // Deal with Timers first.
- mPkgTimers.forEach((t) -> t.onStateChangedLocked(nowElapsed, isCharging));
- // Now update jobs.
- maybeUpdateAllConstraintsLocked();
- }
-
- private void maybeUpdateAllConstraintsLocked() {
- boolean changed = false;
- for (int u = 0; u < mTrackedJobs.numUsers(); ++u) {
- final int userId = mTrackedJobs.keyAt(u);
- for (int p = 0; p < mTrackedJobs.numPackagesForUser(userId); ++p) {
- final String packageName = mTrackedJobs.keyAt(u, p);
- changed |= maybeUpdateConstraintForPkgLocked(userId, packageName);
- }
- }
- if (changed) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
-
- /**
- * Update the CONSTRAINT_WITHIN_QUOTA bit for all of the Jobs for a given package.
- *
- * @return true if at least one job had its bit changed
- */
- private boolean maybeUpdateConstraintForPkgLocked(final int userId,
- @NonNull final String packageName) {
- ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
- if (jobs == null || jobs.size() == 0) {
- return false;
- }
-
- // Quota is the same for all jobs within a package.
- final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket();
- final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket);
- boolean changed = false;
- for (int i = jobs.size() - 1; i >= 0; --i) {
- final JobStatus js = jobs.valueAt(i);
- if (isTopStartedJobLocked(js)) {
- // Job was started while the app was in the TOP state so we should allow it to
- // finish.
- changed |= js.setQuotaConstraintSatisfied(true);
- } else if (realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == getEffectiveStandbyBucket(js)) {
- // An app in the ACTIVE bucket may be out of quota while the job could be in quota
- // for some reason. Therefore, avoid setting the real value here and check each job
- // individually.
- changed |= setConstraintSatisfied(js, realInQuota);
- } else {
- // This job is somehow exempted. Need to determine its own quota status.
- changed |= setConstraintSatisfied(js, isWithinQuotaLocked(js));
- }
- }
- if (!realInQuota) {
- // Don't want to use the effective standby bucket here since that bump the bucket to
- // ACTIVE for one of the jobs, which doesn't help with other jobs that aren't
- // exempted.
- maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
- } else {
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null && alarmListener.isWaiting()) {
- mAlarmManager.cancel(alarmListener);
- // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
- alarmListener.setTriggerTime(0);
- }
- }
- return changed;
- }
-
- private class UidConstraintUpdater implements Consumer<JobStatus> {
- private final UserPackageMap<Integer> mToScheduleStartAlarms = new UserPackageMap<>();
- public boolean wasJobChanged;
-
- @Override
- public void accept(JobStatus jobStatus) {
- wasJobChanged |= setConstraintSatisfied(jobStatus, isWithinQuotaLocked(jobStatus));
- final int userId = jobStatus.getSourceUserId();
- final String packageName = jobStatus.getSourcePackageName();
- final int realStandbyBucket = jobStatus.getStandbyBucket();
- if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null && alarmListener.isWaiting()) {
- mAlarmManager.cancel(alarmListener);
- // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
- alarmListener.setTriggerTime(0);
- }
- } else {
- mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
- }
- }
-
- void postProcess() {
- for (int u = 0; u < mToScheduleStartAlarms.numUsers(); ++u) {
- final int userId = mToScheduleStartAlarms.keyAt(u);
- for (int p = 0; p < mToScheduleStartAlarms.numPackagesForUser(userId); ++p) {
- final String packageName = mToScheduleStartAlarms.keyAt(u, p);
- final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName);
- maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket);
- }
- }
- }
-
- void reset() {
- wasJobChanged = false;
- mToScheduleStartAlarms.clear();
- }
- }
-
- private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater();
-
- private boolean maybeUpdateConstraintForUidLocked(final int uid) {
- mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints);
-
- mUpdateUidConstraints.postProcess();
- boolean changed = mUpdateUidConstraints.wasJobChanged;
- mUpdateUidConstraints.reset();
- return changed;
- }
-
- /**
- * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run
- * again. This should only be called if the package is already out of quota.
- */
- @VisibleForTesting
- void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName,
- final int standbyBucket) {
- if (standbyBucket == NEVER_INDEX) {
- return;
- }
-
- final String pkgString = string(userId, packageName);
- ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
- final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
- standbyBucket);
-
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
- && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
- && isUnderJobCountQuota
- && isUnderTimingSessionCountQuota) {
- // Already in quota. Why was this method called?
- if (DEBUG) {
- Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
- + " even though it already has "
- + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
- + "ms in its quota.");
- }
- if (alarmListener != null) {
- // Cancel any pending alarm.
- mAlarmManager.cancel(alarmListener);
- // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
- alarmListener.setTriggerTime(0);
- }
- mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
- return;
- }
-
- if (alarmListener == null) {
- alarmListener = new QcAlarmListener(userId, packageName);
- mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
- }
-
- // The time this app will have quota again.
- long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
- if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
- // App hit the rate limit.
- inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
- stats.jobRateLimitExpirationTimeElapsed);
- }
- if (!isUnderTimingSessionCountQuota
- && stats.sessionCountInWindow < stats.sessionCountLimit) {
- // App hit the rate limit.
- inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
- stats.sessionRateLimitExpirationTimeElapsed);
- }
- // Only schedule the alarm if:
- // 1. There isn't one currently scheduled
- // 2. The new alarm is significantly earlier than the previous alarm (which could be the
- // case if the package moves into a higher standby bucket). If it's earlier but not
- // significantly so, then we essentially delay the job a few extra minutes.
- // 3. The alarm is after the current alarm by more than the quota buffer.
- // TODO: this might be overengineering. Simplify if proven safe.
- if (!alarmListener.isWaiting()
- || inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
- || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
- if (DEBUG) {
- Slog.d(TAG, "Scheduling start alarm for " + pkgString);
- }
- // If the next time this app will have quota is at least 3 minutes before the
- // alarm is supposed to go off, reschedule the alarm.
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
- ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
- alarmListener.setTriggerTime(inQuotaTimeElapsed);
- } else if (DEBUG) {
- Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
- }
- }
-
- private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) {
- if (!isWithinQuota && jobStatus.getWhenStandbyDeferred() == 0) {
- // Mark that the job is being deferred due to buckets.
- jobStatus.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
- }
- return jobStatus.setQuotaConstraintSatisfied(isWithinQuota);
- }
-
- private final class ChargingTracker extends BroadcastReceiver {
- /**
- * Track whether we're charging. This has a slightly different definition than that of
- * BatteryController.
- */
- private boolean mCharging;
-
- ChargingTracker() {
- }
-
- public void startTracking() {
- IntentFilter filter = new IntentFilter();
-
- // Charging/not charging.
- filter.addAction(BatteryManager.ACTION_CHARGING);
- filter.addAction(BatteryManager.ACTION_DISCHARGING);
- mContext.registerReceiver(this, filter);
-
- // Initialise tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
- }
-
- public boolean isCharging() {
- return mCharging;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- final String action = intent.getAction();
- if (BatteryManager.ACTION_CHARGING.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Received charging intent, fired @ "
- + sElapsedRealtimeClock.millis());
- }
- mCharging = true;
- handleNewChargingStateLocked();
- } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Disconnected from power.");
- }
- mCharging = false;
- handleNewChargingStateLocked();
- }
- }
- }
- }
-
- @VisibleForTesting
- static final class TimingSession {
- // Start timestamp in elapsed realtime timebase.
- public final long startTimeElapsed;
- // End timestamp in elapsed realtime timebase.
- public final long endTimeElapsed;
- // How many background jobs ran during this session.
- public final int bgJobCount;
-
- private final int mHashCode;
-
- TimingSession(long startElapsed, long endElapsed, int bgJobCount) {
- this.startTimeElapsed = startElapsed;
- this.endTimeElapsed = endElapsed;
- this.bgJobCount = bgJobCount;
-
- int hashCode = 0;
- hashCode = 31 * hashCode + hashLong(startTimeElapsed);
- hashCode = 31 * hashCode + hashLong(endTimeElapsed);
- hashCode = 31 * hashCode + bgJobCount;
- mHashCode = hashCode;
- }
-
- @Override
- public String toString() {
- return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
- + "}";
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof TimingSession) {
- TimingSession other = (TimingSession) obj;
- return startTimeElapsed == other.startTimeElapsed
- && endTimeElapsed == other.endTimeElapsed
- && bgJobCount == other.bgJobCount;
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- return mHashCode;
- }
-
- public void dump(IndentingPrintWriter pw) {
- pw.print(startTimeElapsed);
- pw.print(" -> ");
- pw.print(endTimeElapsed);
- pw.print(" (");
- pw.print(endTimeElapsed - startTimeElapsed);
- pw.print("), ");
- pw.print(bgJobCount);
- pw.print(" bg jobs.");
- pw.println();
- }
-
- public void dump(@NonNull ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(StateControllerProto.QuotaController.TimingSession.START_TIME_ELAPSED,
- startTimeElapsed);
- proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED,
- endTimeElapsed);
- proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT,
- bgJobCount);
-
- proto.end(token);
- }
- }
-
- private final class Timer {
- private final Package mPkg;
- private final int mUid;
-
- // List of jobs currently running for this app that started when the app wasn't in the
- // foreground.
- private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
- private long mStartTimeElapsed;
- private int mBgJobCount;
-
- Timer(int uid, int userId, String packageName) {
- mPkg = new Package(userId, packageName);
- mUid = uid;
- }
-
- void startTrackingJobLocked(@NonNull JobStatus jobStatus) {
- if (isTopStartedJobLocked(jobStatus)) {
- // We intentionally don't pay attention to fg state changes after a TOP job has
- // started.
- if (DEBUG) {
- Slog.v(TAG,
- "Timer ignoring " + jobStatus.toShortString() + " because isTop");
- }
- return;
- }
- if (DEBUG) {
- Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
- }
- // Always track jobs, even when charging.
- mRunningBgJobs.add(jobStatus);
- if (shouldTrackLocked()) {
- mBgJobCount++;
- incrementJobCount(mPkg.userId, mPkg.packageName, 1);
- if (mRunningBgJobs.size() == 1) {
- // Started tracking the first job.
- mStartTimeElapsed = sElapsedRealtimeClock.millis();
- // Starting the timer means that all cached execution stats are now incorrect.
- invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
- scheduleCutoff();
- }
- }
- }
-
- void stopTrackingJob(@NonNull JobStatus jobStatus) {
- if (DEBUG) {
- Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString());
- }
- synchronized (mLock) {
- if (mRunningBgJobs.size() == 0) {
- // maybeStopTrackingJobLocked can be called when an app cancels a job, so a
- // timer may not be running when it's asked to stop tracking a job.
- if (DEBUG) {
- Slog.d(TAG, "Timer isn't tracking any jobs but still told to stop");
- }
- return;
- }
- if (mRunningBgJobs.remove(jobStatus)
- && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) {
- emitSessionLocked(sElapsedRealtimeClock.millis());
- cancelCutoff();
- }
- }
- }
-
- /**
- * Stops tracking all jobs and cancels any pending alarms. This should only be called if
- * the Timer is not going to be used anymore.
- */
- void dropEverythingLocked() {
- mRunningBgJobs.clear();
- cancelCutoff();
- }
-
- private void emitSessionLocked(long nowElapsed) {
- if (mBgJobCount <= 0) {
- // Nothing to emit.
- return;
- }
- TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount);
- saveTimingSession(mPkg.userId, mPkg.packageName, ts);
- mBgJobCount = 0;
- // Don't reset the tracked jobs list as we need to keep tracking the current number
- // of jobs.
- // However, cancel the currently scheduled cutoff since it's not currently useful.
- cancelCutoff();
- incrementTimingSessionCount(mPkg.userId, mPkg.packageName);
- }
-
- /**
- * Returns true if the Timer is actively tracking, as opposed to passively ref counting
- * during charging.
- */
- public boolean isActive() {
- synchronized (mLock) {
- return mBgJobCount > 0;
- }
- }
-
- boolean isRunning(JobStatus jobStatus) {
- return mRunningBgJobs.contains(jobStatus);
- }
-
- long getCurrentDuration(long nowElapsed) {
- synchronized (mLock) {
- return !isActive() ? 0 : nowElapsed - mStartTimeElapsed;
- }
- }
-
- int getBgJobCount() {
- synchronized (mLock) {
- return mBgJobCount;
- }
- }
-
- private boolean shouldTrackLocked() {
- return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
- }
-
- void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
- if (isQuotaFree) {
- emitSessionLocked(nowElapsed);
- } else if (!isActive() && shouldTrackLocked()) {
- // Start timing from unplug.
- if (mRunningBgJobs.size() > 0) {
- mStartTimeElapsed = nowElapsed;
- // NOTE: this does have the unfortunate consequence that if the device is
- // repeatedly plugged in and unplugged, or an app changes foreground state
- // very frequently, the job count for a package may be artificially high.
- mBgJobCount = mRunningBgJobs.size();
- incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
- // Starting the timer means that all cached execution stats are now
- // incorrect.
- invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
- // Schedule cutoff since we're now actively tracking for quotas again.
- scheduleCutoff();
- }
- }
- }
-
- void rescheduleCutoff() {
- cancelCutoff();
- scheduleCutoff();
- }
-
- private void scheduleCutoff() {
- // Each package can only be in one standby bucket, so we only need to have one
- // message per timer. We only need to reschedule when restarting timer or when
- // standby bucket changes.
- synchronized (mLock) {
- if (!isActive()) {
- return;
- }
- Message msg = mHandler.obtainMessage(MSG_REACHED_QUOTA, mPkg);
- final long timeRemainingMs = getTimeUntilQuotaConsumedLocked(mPkg.userId,
- mPkg.packageName);
- if (DEBUG) {
- Slog.i(TAG, "Job for " + mPkg + " has " + timeRemainingMs + "ms left.");
- }
- // If the job was running the entire time, then the system would be up, so it's
- // fine to use uptime millis for these messages.
- mHandler.sendMessageDelayed(msg, timeRemainingMs);
- }
- }
-
- private void cancelCutoff() {
- mHandler.removeMessages(MSG_REACHED_QUOTA, mPkg);
- }
-
- public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
- pw.print("Timer{");
- pw.print(mPkg);
- pw.print("} ");
- if (isActive()) {
- pw.print("started at ");
- pw.print(mStartTimeElapsed);
- pw.print(" (");
- pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed);
- pw.print("ms ago)");
- } else {
- pw.print("NOT active");
- }
- pw.print(", ");
- pw.print(mBgJobCount);
- pw.print(" running bg jobs");
- pw.println();
- pw.increaseIndent();
- for (int i = 0; i < mRunningBgJobs.size(); i++) {
- JobStatus js = mRunningBgJobs.valueAt(i);
- if (predicate.test(js)) {
- pw.println(js.toShortString());
- }
- }
- pw.decreaseIndent();
- }
-
- public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) {
- final long token = proto.start(fieldId);
-
- mPkg.writeToProto(proto, StateControllerProto.QuotaController.Timer.PKG);
- proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
- proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
- mStartTimeElapsed);
- proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount);
- for (int i = 0; i < mRunningBgJobs.size(); i++) {
- JobStatus js = mRunningBgJobs.valueAt(i);
- if (predicate.test(js)) {
- js.writeToShortProto(proto,
- StateControllerProto.QuotaController.Timer.RUNNING_JOBS);
- }
- }
-
- proto.end(token);
- }
- }
-
- /**
- * Tracking of app assignments to standby buckets
- */
- final class StandbyTracker extends AppIdleStateChangeListener {
-
- @Override
- public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
- boolean idle, int bucket, int reason) {
- // Update job bookkeeping out of band.
- BackgroundThread.getHandler().post(() -> {
- final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
- if (DEBUG) {
- Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex "
- + bucketIndex);
- }
- synchronized (mLock) {
- ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
- if (jobs == null || jobs.size() == 0) {
- return;
- }
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus js = jobs.valueAt(i);
- js.setStandbyBucket(bucketIndex);
- }
- Timer timer = mPkgTimers.get(userId, packageName);
- if (timer != null && timer.isActive()) {
- timer.rescheduleCutoff();
- }
- if (!mShouldThrottle || maybeUpdateConstraintForPkgLocked(userId,
- packageName)) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- });
- }
-
- @Override
- public void onParoleStateChanged(final boolean isParoleOn) {
- mInParole = isParoleOn;
- if (DEBUG) {
- Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- // Update job bookkeeping out of band.
- BackgroundThread.getHandler().post(() -> {
- synchronized (mLock) {
- maybeUpdateAllConstraintsLocked();
- }
- });
- }
- }
-
- private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
- private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() {
- public boolean test(TimingSession ts) {
- return ts.endTimeElapsed <= sElapsedRealtimeClock.millis() - MAX_PERIOD_MS;
- }
- };
-
- @Override
- public void accept(List<TimingSession> sessions) {
- if (sessions != null) {
- // Remove everything older than MAX_PERIOD_MS time ago.
- sessions.removeIf(mTooOld);
- }
- }
- }
-
- private final DeleteTimingSessionsFunctor mDeleteOldSessionsFunctor =
- new DeleteTimingSessionsFunctor();
-
- @VisibleForTesting
- void deleteObsoleteSessionsLocked() {
- mTimingSessions.forEach(mDeleteOldSessionsFunctor);
- }
-
- private class QcHandler extends Handler {
- QcHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- synchronized (mLock) {
- switch (msg.what) {
- case MSG_REACHED_QUOTA: {
- Package pkg = (Package) msg.obj;
- if (DEBUG) {
- Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
- }
-
- long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
- pkg.packageName);
- if (timeRemainingMs <= 50) {
- // Less than 50 milliseconds left. Start process of shutting down jobs.
- if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
- if (maybeUpdateConstraintForPkgLocked(pkg.userId, pkg.packageName)) {
- mStateChangedListener.onControllerStateChanged();
- }
- } else {
- // This could potentially happen if an old session phases out while a
- // job is currently running.
- // Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
- timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
- pkg.packageName);
- if (DEBUG) {
- Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left.");
- }
- sendMessageDelayed(rescheduleMsg, timeRemainingMs);
- }
- break;
- }
- case MSG_CLEAN_UP_SESSIONS:
- if (DEBUG) {
- Slog.d(TAG, "Cleaning up timing sessions.");
- }
- deleteObsoleteSessionsLocked();
- maybeScheduleCleanupAlarmLocked();
-
- break;
- case MSG_CHECK_PACKAGE: {
- String packageName = (String) msg.obj;
- int userId = msg.arg1;
- if (DEBUG) {
- Slog.d(TAG, "Checking pkg " + string(userId, packageName));
- }
- if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
- mStateChangedListener.onControllerStateChanged();
- }
- break;
- }
- case MSG_UID_PROCESS_STATE_CHANGED: {
- final int uid = msg.arg1;
- final int procState = msg.arg2;
- final int userId = UserHandle.getUserId(uid);
- final long nowElapsed = sElapsedRealtimeClock.millis();
-
- synchronized (mLock) {
- boolean isQuotaFree;
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- mForegroundUids.put(uid, true);
- isQuotaFree = true;
- } else {
- mForegroundUids.delete(uid);
- isQuotaFree = false;
- }
- // Update Timers first.
- if (mPkgTimers.indexOfKey(userId) >= 0) {
- ArraySet<String> packages = mUidToPackageCache.get(uid);
- if (packages == null) {
- try {
- String[] pkgs = AppGlobals.getPackageManager()
- .getPackagesForUid(uid);
- if (pkgs != null) {
- for (String pkg : pkgs) {
- mUidToPackageCache.add(uid, pkg);
- }
- packages = mUidToPackageCache.get(uid);
- }
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Failed to get package list", e);
- }
- }
- if (packages != null) {
- for (int i = packages.size() - 1; i >= 0; --i) {
- Timer t = mPkgTimers.get(userId, packages.valueAt(i));
- if (t != null) {
- t.onStateChangedLocked(nowElapsed, isQuotaFree);
- }
- }
- }
- }
- if (maybeUpdateConstraintForUidLocked(uid)) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- break;
- }
- }
- }
- }
- }
-
- private class QcAlarmListener implements AlarmManager.OnAlarmListener {
- private final int mUserId;
- private final String mPackageName;
- private volatile long mTriggerTimeElapsed;
-
- QcAlarmListener(int userId, String packageName) {
- mUserId = userId;
- mPackageName = packageName;
- }
-
- boolean isWaiting() {
- return mTriggerTimeElapsed > 0;
- }
-
- void setTriggerTime(long timeElapsed) {
- mTriggerTimeElapsed = timeElapsed;
- }
-
- long getTriggerTimeElapsed() {
- return mTriggerTimeElapsed;
- }
-
- @Override
- public void onAlarm() {
- mHandler.obtainMessage(MSG_CHECK_PACKAGE, mUserId, 0, mPackageName).sendToTarget();
- mTriggerTimeElapsed = 0;
- }
- }
-
- @VisibleForTesting
- class QcConstants extends ContentObserver {
- private ContentResolver mResolver;
- private final KeyValueListParser mParser = new KeyValueListParser(',');
-
- private static final String KEY_ALLOWED_TIME_PER_PERIOD_MS = "allowed_time_per_period_ms";
- private static final String KEY_IN_QUOTA_BUFFER_MS = "in_quota_buffer_ms";
- private static final String KEY_WINDOW_SIZE_ACTIVE_MS = "window_size_active_ms";
- private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms";
- private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms";
- private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms";
- private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms";
- private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active";
- private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
- private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
- private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
- private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
- private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
- "max_job_count_per_rate_limiting_window";
- private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active";
- private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
- private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
- private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
- private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
- "max_session_count_per_rate_limiting_window";
- private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
- "timing_session_coalescing_duration_ms";
-
- private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
- 10 * 60 * 1000L; // 10 minutes
- private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
- 30 * 1000L; // 30 seconds
- private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
- private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
- 2 * 60 * 60 * 1000L; // 2 hours
- private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
- 8 * 60 * 60 * 1000L; // 8 hours
- private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
- 24 * 60 * 60 * 1000L; // 24 hours
- private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
- 4 * HOUR_IN_MILLIS;
- private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
- 10 * MINUTE_IN_MILLIS;
- private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
- private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = // 20/window = 120/hr = 1/session
- DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
- private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
- (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
- private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
- (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
- private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
- (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
- private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
- 20; // 120/hr
- private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
- 10; // 5/hr
- private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT =
- 8; // 1/hr
- private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
- 3; // .125/hr
- private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
- private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
-
- /** How much time each app will have to run jobs within their standby bucket window. */
- public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
-
- /**
- * How much time the package should have before transitioning from out-of-quota to in-quota.
- * This should not affect processing if the package is already in-quota.
- */
- public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS;
-
- /**
- * The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
- * WINDOW_SIZE_MS.
- */
- public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS;
-
- /**
- * The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
- * WINDOW_SIZE_MS.
- */
- public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS;
-
- /**
- * The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
- * WINDOW_SIZE_MS.
- */
- public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS;
-
- /**
- * The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
- * WINDOW_SIZE_MS.
- */
- public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS;
-
- /**
- * The maximum amount of time an app can have its jobs running within a 24 hour window.
- */
- public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS;
-
- /**
- * The maximum number of jobs an app can run within this particular standby bucket's
- * window size.
- */
- public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE;
-
- /**
- * The maximum number of jobs an app can run within this particular standby bucket's
- * window size.
- */
- public int MAX_JOB_COUNT_WORKING = DEFAULT_MAX_JOB_COUNT_WORKING;
-
- /**
- * The maximum number of jobs an app can run within this particular standby bucket's
- * window size.
- */
- public int MAX_JOB_COUNT_FREQUENT = DEFAULT_MAX_JOB_COUNT_FREQUENT;
-
- /**
- * The maximum number of jobs an app can run within this particular standby bucket's
- * window size.
- */
- public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
-
- /** The period of time used to rate limit recently run jobs. */
- public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
-
- /**
- * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}.
- */
- public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
- DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
-
- /**
- * The maximum number of {@link TimingSession}s an app can run within this particular
- * standby bucket's window size.
- */
- public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE;
-
- /**
- * The maximum number of {@link TimingSession}s an app can run within this particular
- * standby bucket's window size.
- */
- public int MAX_SESSION_COUNT_WORKING = DEFAULT_MAX_SESSION_COUNT_WORKING;
-
- /**
- * The maximum number of {@link TimingSession}s an app can run within this particular
- * standby bucket's window size.
- */
- public int MAX_SESSION_COUNT_FREQUENT = DEFAULT_MAX_SESSION_COUNT_FREQUENT;
-
- /**
- * The maximum number of {@link TimingSession}s an app can run within this particular
- * standby bucket's window size.
- */
- public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE;
-
- /**
- * The maximum number of {@link TimingSession}s that can run within the past
- * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
- */
- public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
- DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
-
- /**
- * Treat two distinct {@link TimingSession}s as the same if they start and end within this
- * amount of time of each other.
- */
- public long TIMING_SESSION_COALESCING_DURATION_MS =
- DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
-
- // Safeguards
-
- /** The minimum number of jobs that any bucket will be allowed to run within its window. */
- private static final int MIN_BUCKET_JOB_COUNT = 10;
-
- /**
- * The minimum number of {@link TimingSession}s that any bucket will be allowed to run
- * within its window.
- */
- private static final int MIN_BUCKET_SESSION_COUNT = 1;
-
- /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */
- private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS;
-
- /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
- private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10;
-
- /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
- private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10;
-
- /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */
- private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS;
-
- QcConstants(Handler handler) {
- super(handler);
- }
-
- private void start(ContentResolver resolver) {
- mResolver = resolver;
- mResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS), false, this);
- updateConstants();
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- final String constants = Settings.Global.getString(
- mResolver, Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS);
-
- try {
- mParser.setString(constants);
- } catch (Exception e) {
- // Failed to parse the settings string, log this and move on with defaults.
- Slog.e(TAG, "Bad jobscheduler quota controller settings", e);
- }
-
- ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis(
- KEY_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
- IN_QUOTA_BUFFER_MS = mParser.getDurationMillis(
- KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS);
- WINDOW_SIZE_ACTIVE_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_ACTIVE_MS, DEFAULT_WINDOW_SIZE_ACTIVE_MS);
- WINDOW_SIZE_WORKING_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS);
- WINDOW_SIZE_FREQUENT_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS);
- WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS);
- MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
- KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS);
- MAX_JOB_COUNT_ACTIVE = mParser.getInt(
- KEY_MAX_JOB_COUNT_ACTIVE, DEFAULT_MAX_JOB_COUNT_ACTIVE);
- MAX_JOB_COUNT_WORKING = mParser.getInt(
- KEY_MAX_JOB_COUNT_WORKING, DEFAULT_MAX_JOB_COUNT_WORKING);
- MAX_JOB_COUNT_FREQUENT = mParser.getInt(
- KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
- MAX_JOB_COUNT_RARE = mParser.getInt(
- KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
- RATE_LIMITING_WINDOW_MS = mParser.getLong(
- KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
- MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
- KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
- DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
- MAX_SESSION_COUNT_ACTIVE = mParser.getInt(
- KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
- MAX_SESSION_COUNT_WORKING = mParser.getInt(
- KEY_MAX_SESSION_COUNT_WORKING, DEFAULT_MAX_SESSION_COUNT_WORKING);
- MAX_SESSION_COUNT_FREQUENT = mParser.getInt(
- KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
- MAX_SESSION_COUNT_RARE = mParser.getInt(
- KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
- MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
- KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
- DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
- TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
- KEY_TIMING_SESSION_COALESCING_DURATION_MS,
- DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
-
- updateConstants();
- }
-
- @VisibleForTesting
- void updateConstants() {
- synchronized (mLock) {
- boolean changed = false;
-
- long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
- Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
- if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
- mMaxExecutionTimeMs = newMaxExecutionTimeMs;
- mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
- changed = true;
- }
- long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
- Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
- if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
- mAllowedTimePerPeriodMs = newAllowedTimeMs;
- mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
- changed = true;
- }
- // Make sure quota buffer is non-negative, not greater than allowed time per period,
- // and no more than 5 minutes.
- long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs,
- Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS)));
- if (mQuotaBufferMs != newQuotaBufferMs) {
- mQuotaBufferMs = newQuotaBufferMs;
- mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
- mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
- changed = true;
- }
- long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
- if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
- mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs;
- changed = true;
- }
- long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
- if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) {
- mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs;
- changed = true;
- }
- long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
- if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) {
- mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs;
- changed = true;
- }
- long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS));
- if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) {
- mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
- changed = true;
- }
- long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
- Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
- if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
- mRateLimitingWindowMs = newRateLimitingWindowMs;
- changed = true;
- }
- int newMaxJobCountPerRateLimitingWindow = Math.max(
- MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
- if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
- mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
- changed = true;
- }
- int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
- if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
- mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
- changed = true;
- }
- int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING);
- if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
- mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
- changed = true;
- }
- int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT);
- if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
- mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
- changed = true;
- }
- int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
- if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
- mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
- changed = true;
- }
- int newMaxSessionCountPerRateLimitPeriod = Math.max(
- MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
- if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
- mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
- changed = true;
- }
- int newActiveMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_ACTIVE);
- if (mMaxBucketSessionCounts[ACTIVE_INDEX] != newActiveMaxSessionCount) {
- mMaxBucketSessionCounts[ACTIVE_INDEX] = newActiveMaxSessionCount;
- changed = true;
- }
- int newWorkingMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_WORKING);
- if (mMaxBucketSessionCounts[WORKING_INDEX] != newWorkingMaxSessionCount) {
- mMaxBucketSessionCounts[WORKING_INDEX] = newWorkingMaxSessionCount;
- changed = true;
- }
- int newFrequentMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_FREQUENT);
- if (mMaxBucketSessionCounts[FREQUENT_INDEX] != newFrequentMaxSessionCount) {
- mMaxBucketSessionCounts[FREQUENT_INDEX] = newFrequentMaxSessionCount;
- changed = true;
- }
- int newRareMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_RARE);
- if (mMaxBucketSessionCounts[RARE_INDEX] != newRareMaxSessionCount) {
- mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount;
- changed = true;
- }
- long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS,
- Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS));
- if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) {
- mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs;
- changed = true;
- }
-
- if (changed && mShouldThrottle) {
- // Update job bookkeeping out of band.
- BackgroundThread.getHandler().post(() -> {
- synchronized (mLock) {
- invalidateAllExecutionStatsLocked();
- maybeUpdateAllConstraintsLocked();
- }
- });
- }
- }
- }
-
- private void dump(IndentingPrintWriter pw) {
- pw.println();
- pw.println("QuotaController:");
- pw.increaseIndent();
- pw.printPair(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println();
- pw.printPair(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
- pw.printPair(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
- pw.printPair(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
- pw.printPair(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println();
- pw.printPair(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println();
- pw.printPair(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println();
- pw.printPair(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println();
- pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
- pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
- pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
- pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
- pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
- pw.printPair(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
- pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
- pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
- pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
- pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
- pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
- TIMING_SESSION_COALESCING_DURATION_MS).println();
- pw.decreaseIndent();
- }
-
- private void dump(ProtoOutputStream proto) {
- final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER);
- proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS,
- ALLOWED_TIME_PER_PERIOD_MS);
- proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS);
- proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS,
- WINDOW_SIZE_ACTIVE_MS);
- proto.write(ConstantsProto.QuotaController.WORKING_WINDOW_SIZE_MS,
- WINDOW_SIZE_WORKING_MS);
- proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS,
- WINDOW_SIZE_FREQUENT_MS);
- proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS);
- proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
- MAX_EXECUTION_TIME_MS);
- proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE);
- proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING,
- MAX_JOB_COUNT_WORKING);
- proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
- MAX_JOB_COUNT_FREQUENT);
- proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
- proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
- RATE_LIMITING_WINDOW_MS);
- proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
- proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE,
- MAX_SESSION_COUNT_ACTIVE);
- proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING,
- MAX_SESSION_COUNT_WORKING);
- proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_FREQUENT,
- MAX_SESSION_COUNT_FREQUENT);
- proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
- MAX_SESSION_COUNT_RARE);
- proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
- proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
- TIMING_SESSION_COALESCING_DURATION_MS);
- proto.end(qcToken);
- }
- }
-
- //////////////////////// TESTING HELPERS /////////////////////////////
-
- @VisibleForTesting
- long getAllowedTimePerPeriodMs() {
- return mAllowedTimePerPeriodMs;
- }
-
- @VisibleForTesting
- @NonNull
- int[] getBucketMaxJobCounts() {
- return mMaxBucketJobCounts;
- }
-
- @VisibleForTesting
- @NonNull
- int[] getBucketMaxSessionCounts() {
- return mMaxBucketSessionCounts;
- }
-
- @VisibleForTesting
- @NonNull
- long[] getBucketWindowSizes() {
- return mBucketPeriodsMs;
- }
-
- @VisibleForTesting
- @NonNull
- SparseBooleanArray getForegroundUids() {
- return mForegroundUids;
- }
-
- @VisibleForTesting
- @NonNull
- Handler getHandler() {
- return mHandler;
- }
-
- @VisibleForTesting
- long getInQuotaBufferMs() {
- return mQuotaBufferMs;
- }
-
- @VisibleForTesting
- long getMaxExecutionTimeMs() {
- return mMaxExecutionTimeMs;
- }
-
- @VisibleForTesting
- int getMaxJobCountPerRateLimitingWindow() {
- return mMaxJobCountPerRateLimitingWindow;
- }
-
- @VisibleForTesting
- int getMaxSessionCountPerRateLimitingWindow() {
- return mMaxSessionCountPerRateLimitingWindow;
- }
-
- @VisibleForTesting
- long getRateLimitingWindowMs() {
- return mRateLimitingWindowMs;
- }
-
- @VisibleForTesting
- long getTimingSessionCoalescingDurationMs() {
- return mTimingSessionCoalescingDurationMs;
- }
-
- @VisibleForTesting
- @Nullable
- List<TimingSession> getTimingSessions(int userId, String packageName) {
- return mTimingSessions.get(userId, packageName);
- }
-
- @VisibleForTesting
- @NonNull
- QcConstants getQcConstants() {
- return mQcConstants;
- }
-
- //////////////////////////// DATA DUMP //////////////////////////////
-
- @Override
- public void dumpControllerStateLocked(final IndentingPrintWriter pw,
- final Predicate<JobStatus> predicate) {
- pw.println("Is throttling: " + mShouldThrottle);
- pw.println("Is charging: " + mChargeTracker.isCharging());
- pw.println("In parole: " + mInParole);
- pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
- pw.println();
-
- pw.print("Foreground UIDs: ");
- pw.println(mForegroundUids.toString());
- pw.println();
-
- pw.println("Cached UID->package map:");
- pw.increaseIndent();
- for (int i = 0; i < mUidToPackageCache.size(); ++i) {
- final int uid = mUidToPackageCache.keyAt(i);
- pw.print(uid);
- pw.print(": ");
- pw.println(mUidToPackageCache.get(uid));
- }
- pw.decreaseIndent();
- pw.println();
-
- mTrackedJobs.forEach((jobs) -> {
- for (int j = 0; j < jobs.size(); j++) {
- final JobStatus js = jobs.valueAt(j);
- if (!predicate.test(js)) {
- continue;
- }
- pw.print("#");
- js.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, js.getSourceUid());
- if (mTopStartedJobs.contains(js)) {
- pw.print(" (TOP)");
- }
- pw.println();
-
- pw.increaseIndent();
- pw.print(JobStatus.bucketName(getEffectiveStandbyBucket(js)));
- pw.print(", ");
- if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
- pw.print("within quota");
- } else {
- pw.print("not within quota");
- }
- pw.print(", ");
- pw.print(getRemainingExecutionTimeLocked(js));
- pw.print("ms remaining in quota");
- pw.decreaseIndent();
- pw.println();
- }
- });
-
- pw.println();
- for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
- final int userId = mPkgTimers.keyAt(u);
- for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
- final String pkgName = mPkgTimers.keyAt(u, p);
- mPkgTimers.valueAt(u, p).dump(pw, predicate);
- pw.println();
- List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
- if (sessions != null) {
- pw.increaseIndent();
- pw.println("Saved sessions:");
- pw.increaseIndent();
- for (int j = sessions.size() - 1; j >= 0; j--) {
- TimingSession session = sessions.get(j);
- session.dump(pw);
- }
- pw.decreaseIndent();
- pw.decreaseIndent();
- pw.println();
- }
- }
- }
-
- pw.println("Cached execution stats:");
- pw.increaseIndent();
- for (int u = 0; u < mExecutionStatsCache.numUsers(); ++u) {
- final int userId = mExecutionStatsCache.keyAt(u);
- for (int p = 0; p < mExecutionStatsCache.numPackagesForUser(userId); ++p) {
- final String pkgName = mExecutionStatsCache.keyAt(u, p);
- ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
-
- pw.println(string(userId, pkgName));
- pw.increaseIndent();
- for (int i = 0; i < stats.length; ++i) {
- ExecutionStats executionStats = stats[i];
- if (executionStats != null) {
- pw.print(JobStatus.bucketName(i));
- pw.print(": ");
- pw.println(executionStats);
- }
- }
- pw.decreaseIndent();
- }
- }
- pw.decreaseIndent();
-
- pw.println();
- pw.println("In quota alarms:");
- pw.increaseIndent();
- for (int u = 0; u < mInQuotaAlarmListeners.numUsers(); ++u) {
- final int userId = mInQuotaAlarmListeners.keyAt(u);
- for (int p = 0; p < mInQuotaAlarmListeners.numPackagesForUser(userId); ++p) {
- final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);
-
- pw.print(string(userId, pkgName));
- pw.print(": ");
- if (alarmListener.isWaiting()) {
- pw.println(alarmListener.getTriggerTimeElapsed());
- } else {
- pw.println("NOT WAITING");
- }
- }
- }
- pw.decreaseIndent();
- }
-
- @Override
- public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
- Predicate<JobStatus> predicate) {
- final long token = proto.start(fieldId);
- final long mToken = proto.start(StateControllerProto.QUOTA);
-
- proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
- proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
- proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
- sElapsedRealtimeClock.millis());
-
- for (int i = 0; i < mForegroundUids.size(); ++i) {
- proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS,
- mForegroundUids.keyAt(i));
- }
-
- mTrackedJobs.forEach((jobs) -> {
- for (int j = 0; j < jobs.size(); j++) {
- final JobStatus js = jobs.valueAt(j);
- if (!predicate.test(js)) {
- continue;
- }
- final long jsToken = proto.start(
- StateControllerProto.QuotaController.TRACKED_JOBS);
- js.writeToShortProto(proto,
- StateControllerProto.QuotaController.TrackedJob.INFO);
- proto.write(StateControllerProto.QuotaController.TrackedJob.SOURCE_UID,
- js.getSourceUid());
- proto.write(
- StateControllerProto.QuotaController.TrackedJob.EFFECTIVE_STANDBY_BUCKET,
- getEffectiveStandbyBucket(js));
- proto.write(StateControllerProto.QuotaController.TrackedJob.IS_TOP_STARTED_JOB,
- mTopStartedJobs.contains(js));
- proto.write(StateControllerProto.QuotaController.TrackedJob.HAS_QUOTA,
- js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
- proto.write(StateControllerProto.QuotaController.TrackedJob.REMAINING_QUOTA_MS,
- getRemainingExecutionTimeLocked(js));
- proto.end(jsToken);
- }
- });
-
- for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
- final int userId = mPkgTimers.keyAt(u);
- for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
- final String pkgName = mPkgTimers.keyAt(u, p);
- final long psToken = proto.start(
- StateControllerProto.QuotaController.PACKAGE_STATS);
- mPkgTimers.valueAt(u, p).dump(proto,
- StateControllerProto.QuotaController.PackageStats.TIMER, predicate);
-
- List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
- if (sessions != null) {
- for (int j = sessions.size() - 1; j >= 0; j--) {
- TimingSession session = sessions.get(j);
- session.dump(proto,
- StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS);
- }
- }
-
- ExecutionStats[] stats = mExecutionStatsCache.get(userId, pkgName);
- if (stats != null) {
- for (int bucketIndex = 0; bucketIndex < stats.length; ++bucketIndex) {
- ExecutionStats es = stats[bucketIndex];
- if (es == null) {
- continue;
- }
- final long esToken = proto.start(
- StateControllerProto.QuotaController.PackageStats.EXECUTION_STATS);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.STANDBY_BUCKET,
- bucketIndex);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.EXPIRATION_TIME_ELAPSED,
- es.expirationTimeElapsed);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS,
- es.windowSizeMs);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT,
- es.jobCountLimit);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT,
- es.sessionCountLimit);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS,
- es.executionTimeInWindowMs);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_WINDOW,
- es.bgJobCountInWindow);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_MAX_PERIOD_MS,
- es.executionTimeInMaxPeriodMs);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_MAX_PERIOD,
- es.bgJobCountInMaxPeriod);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW,
- es.sessionCountInWindow);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
- es.inQuotaTimeElapsed);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED,
- es.jobRateLimitExpirationTimeElapsed);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW,
- es.jobCountInRateLimitingWindow);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED,
- es.sessionRateLimitExpirationTimeElapsed);
- proto.write(
- StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW,
- es.sessionCountInRateLimitingWindow);
- proto.end(esToken);
- }
- }
-
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, pkgName);
- if (alarmListener != null) {
- final long alToken = proto.start(
- StateControllerProto.QuotaController.PackageStats.IN_QUOTA_ALARM_LISTENER);
- proto.write(StateControllerProto.QuotaController.AlarmListener.IS_WAITING,
- alarmListener.isWaiting());
- proto.write(
- StateControllerProto.QuotaController.AlarmListener.TRIGGER_TIME_ELAPSED,
- alarmListener.getTriggerTimeElapsed());
- proto.end(alToken);
- }
-
- proto.end(psToken);
- }
- }
-
- proto.end(mToken);
- proto.end(token);
- }
-
- @Override
- public void dumpConstants(IndentingPrintWriter pw) {
- mQcConstants.dump(pw);
- }
-
- @Override
- public void dumpConstants(ProtoOutputStream proto) {
- mQcConstants.dump(proto);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
deleted file mode 100644
index 51be38b..0000000
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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 static com.android.server.job.JobSchedulerService.DEBUG;
-
-import android.content.Context;
-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.JobSchedulerService.Constants;
-import com.android.server.job.StateChangedListener;
-
-import java.util.function.Predicate;
-
-/**
- * Incorporates shared controller logic between the various controllers of the JobManager.
- * These are solely responsible for tracking a list of jobs, and notifying the JM when these
- * are ready to run, or whether they must be stopped.
- */
-public abstract class StateController {
- private static final String TAG = "JobScheduler.SC";
-
- protected final JobSchedulerService mService;
- protected final StateChangedListener mStateChangedListener;
- protected final Context mContext;
- protected final Object mLock;
- protected final Constants mConstants;
-
- StateController(JobSchedulerService service) {
- mService = service;
- mStateChangedListener = service;
- mContext = service.getTestableContext();
- mLock = service.getLock();
- mConstants = service.getConstants();
- }
-
- /**
- * Called when the system boot phase has reached
- * {@link com.android.server.SystemService#PHASE_SYSTEM_SERVICES_READY}.
- */
- public void onSystemServicesReady() {
- }
-
- /**
- * Implement the logic here to decide whether a job should be tracked by this controller.
- * This logic is put here so the JobManager can be completely agnostic of Controller logic.
- * Also called when updating a task, so implementing controllers have to be aware of
- * preexisting tasks.
- */
- public abstract void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob);
-
- /**
- * Optionally implement logic here to prepare the job to be executed.
- */
- public void prepareForExecutionLocked(JobStatus jobStatus) {
- }
-
- /**
- * Remove task - this will happen if the task is cancelled, completed, etc.
- */
- public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
- boolean forUpdate);
-
- /**
- * Called when a new job is being created to reschedule an old failed job.
- */
- public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) {
- }
-
- /**
- * Called when the JobScheduler.Constants are updated.
- */
- public void onConstantsUpdatedLocked() {
- }
-
- /** Called when a package is uninstalled from the device (not for an update). */
- public void onAppRemovedLocked(String packageName, int uid) {
- }
-
- /** Called when a user is removed from the device. */
- public void onUserRemovedLocked(int userId) {
- }
-
- /**
- * Called when JobSchedulerService has determined that the job is not ready to be run. The
- * Controller can evaluate if it can or should do something to promote this job's readiness.
- */
- public void evaluateStateLocked(JobStatus jobStatus) {
- }
-
- /**
- * Called when something with the UID has changed. The controller should re-evaluate any
- * internal state tracking dependent on this UID.
- */
- public void reevaluateStateLocked(int uid) {
- }
-
- protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
- // This is very cheap to check (just a few conditions on data in JobStatus).
- final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint);
- if (DEBUG) {
- Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString()
- + " constraint=" + constraint
- + " readyWithConstraint=" + jobWouldBeReady);
- }
- if (!jobWouldBeReady) {
- // If the job wouldn't be ready, nothing to do here.
- return false;
- }
-
- // This is potentially more expensive since JSS may have to query component
- // presence.
- return mService.areComponentsInPlaceLocked(jobStatus);
- }
-
- public abstract void dumpControllerStateLocked(IndentingPrintWriter pw,
- Predicate<JobStatus> predicate);
- public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
- Predicate<JobStatus> predicate);
-
- /** Dump any internal constants the Controller may have. */
- public void dumpConstants(IndentingPrintWriter pw) {
- }
-
- /** Dump any internal constants the Controller may have. */
- public void dumpConstants(ProtoOutputStream proto) {
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
deleted file mode 100644
index 51187df..0000000
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * 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.job.controllers;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-import com.android.server.storage.DeviceStorageMonitorService;
-
-import java.util.function.Predicate;
-
-/**
- * Simple controller that tracks the status of the device's storage.
- */
-public final class StorageController extends StateController {
- private static final String TAG = "JobScheduler.Storage";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<JobStatus>();
- private final StorageTracker mStorageTracker;
-
- @VisibleForTesting
- public StorageTracker getTracker() {
- return mStorageTracker;
- }
-
- public StorageController(JobSchedulerService service) {
- super(service);
- mStorageTracker = new StorageTracker();
- mStorageTracker.startTracking();
- }
-
- @Override
- public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
- if (taskStatus.hasStorageNotLowConstraint()) {
- mTrackedTasks.add(taskStatus);
- taskStatus.setTrackingController(JobStatus.TRACKING_STORAGE);
- taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
- }
- }
-
- @Override
- public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
- boolean forUpdate) {
- if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) {
- mTrackedTasks.remove(taskStatus);
- }
- }
-
- private void maybeReportNewStorageState() {
- final boolean storageNotLow = mStorageTracker.isStorageNotLow();
- boolean reportChange = false;
- synchronized (mLock) {
- for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
- final JobStatus ts = mTrackedTasks.valueAt(i);
- reportChange |= ts.setStorageNotLowConstraintSatisfied(storageNotLow);
- }
- }
- if (storageNotLow) {
- // Tell the scheduler that any ready jobs should be flushed.
- mStateChangedListener.onRunJobNow(null);
- } else if (reportChange) {
- // Let the scheduler know that state has changed. This may or may not result in an
- // execution.
- mStateChangedListener.onControllerStateChanged();
- }
- }
-
- public final class StorageTracker extends BroadcastReceiver {
- /**
- * Track whether storage is low.
- */
- private boolean mStorageLow;
- /** Sequence number of last broadcast. */
- private int mLastStorageSeq = -1;
-
- public StorageTracker() {
- }
-
- public void startTracking() {
- IntentFilter filter = new IntentFilter();
-
- // Storage status. Just need to register, since STORAGE_LOW is a sticky
- // broadcast we will receive that if it is currently active.
- filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
- filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
- mContext.registerReceiver(this, filter);
- }
-
- public boolean isStorageNotLow() {
- return !mStorageLow;
- }
-
- public int getSeq() {
- return mLastStorageSeq;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- onReceiveInternal(intent);
- }
-
- @VisibleForTesting
- public void onReceiveInternal(Intent intent) {
- final String action = intent.getAction();
- mLastStorageSeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
- mLastStorageSeq);
- if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Available storage too low to do work. @ "
- + sElapsedRealtimeClock.millis());
- }
- mStorageLow = true;
- maybeReportNewStorageState();
- } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Available storage high enough to do work. @ "
- + sElapsedRealtimeClock.millis());
- }
- mStorageLow = false;
- maybeReportNewStorageState();
- }
- }
- }
-
- @Override
- public void dumpControllerStateLocked(IndentingPrintWriter pw,
- Predicate<JobStatus> predicate) {
- pw.println("Not low: " + mStorageTracker.isStorageNotLow());
- pw.println("Sequence: " + mStorageTracker.getSeq());
- 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.STORAGE);
-
- proto.write(StateControllerProto.StorageController.IS_STORAGE_NOT_LOW,
- mStorageTracker.isStorageNotLow());
- proto.write(StateControllerProto.StorageController.LAST_BROADCAST_SEQUENCE_NUMBER,
- mStorageTracker.getSeq());
-
- 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.StorageController.TRACKED_JOBS);
- js.writeToShortProto(proto, StateControllerProto.StorageController.TrackedJob.INFO);
- proto.write(StateControllerProto.StorageController.TrackedJob.SOURCE_UID,
- js.getSourceUid());
- proto.end(jsToken);
- }
-
- proto.end(mToken);
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
deleted file mode 100644
index 4c11947..0000000
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * 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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.AlarmManager.OnAlarmListener;
-import android.content.Context;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.function.Predicate;
-
-/**
- * This class sets an alarm for the next expiring job, and determines whether a job's minimum
- * delay has been satisfied.
- */
-public final class TimeController extends StateController {
- private static final String TAG = "JobScheduler.Time";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- /** Deadline alarm tag for logging purposes */
- private final String DEADLINE_TAG = "*job.deadline*";
- /** Delay alarm tag for logging purposes */
- private final String DELAY_TAG = "*job.delay*";
-
- private long mNextJobExpiredElapsedMillis;
- private long mNextDelayExpiredElapsedMillis;
-
- private final boolean mChainedAttributionEnabled;
-
- private AlarmManager mAlarmService = null;
- /** List of tracked jobs, sorted asc. by deadline */
- private final List<JobStatus> mTrackedJobs = new LinkedList<>();
-
- public TimeController(JobSchedulerService service) {
- super(service);
-
- mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
- mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
- mChainedAttributionEnabled = mService.isChainedAttributionEnabled();
- }
-
- /**
- * Check if the job has a timing constraint, and if so determine where to insert it in our
- * list.
- */
- @Override
- public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
- if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
- maybeStopTrackingJobLocked(job, null, false);
-
- // First: check the constraints now, because if they are already satisfied
- // then there is no need to track it. This gives us a fast path for a common
- // pattern of having a job with a 0 deadline constraint ("run immediately").
- // Unlike most controllers, once one of our constraints has been satisfied, it
- // will never be unsatisfied (our time base can not go backwards).
- final long nowElapsedMillis = sElapsedRealtimeClock.millis();
- if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
- return;
- } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
- nowElapsedMillis)) {
- if (!job.hasDeadlineConstraint()) {
- // If it doesn't have a deadline, we'll never have to touch it again.
- return;
- }
- }
-
- boolean isInsert = false;
- ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
- while (it.hasPrevious()) {
- JobStatus ts = it.previous();
- if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
- // Insert
- isInsert = true;
- break;
- }
- }
- if (isInsert) {
- it.next();
- }
- it.add(job);
-
- job.setTrackingController(JobStatus.TRACKING_TIME);
- WorkSource ws = deriveWorkSource(job.getSourceUid(), job.getSourcePackageName());
-
- // Only update alarms if the job would be ready with the relevant timing constraint
- // satisfied.
- if (job.hasTimingDelayConstraint()
- && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
- maybeUpdateDelayAlarmLocked(job.getEarliestRunTime(), ws);
- }
- if (job.hasDeadlineConstraint()
- && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
- maybeUpdateDeadlineAlarmLocked(job.getLatestRunTimeElapsed(), ws);
- }
- }
- }
-
- /**
- * When we stop tracking a job, we only need to update our alarms if the job we're no longer
- * tracking was the one our alarms were based off of.
- */
- @Override
- public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob,
- boolean forUpdate) {
- if (job.clearTrackingController(JobStatus.TRACKING_TIME)) {
- if (mTrackedJobs.remove(job)) {
- checkExpiredDelaysAndResetAlarm();
- checkExpiredDeadlinesAndResetAlarm();
- }
- }
- }
-
- @Override
- public void evaluateStateLocked(JobStatus job) {
- final long nowElapsedMillis = sElapsedRealtimeClock.millis();
-
- // Check deadline constraint first because if it's satisfied, we avoid a little bit of
- // unnecessary processing of the timing delay.
- if (job.hasDeadlineConstraint()
- && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)
- && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) {
- if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
- checkExpiredDeadlinesAndResetAlarm();
- checkExpiredDelaysAndResetAlarm();
- } else {
- final boolean isAlarmForJob =
- job.getLatestRunTimeElapsed() == mNextJobExpiredElapsedMillis;
- final boolean wouldBeReady = wouldBeReadyWithConstraintLocked(
- job, JobStatus.CONSTRAINT_DEADLINE);
- if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) {
- checkExpiredDeadlinesAndResetAlarm();
- }
- }
- }
- if (job.hasTimingDelayConstraint()
- && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)
- && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
- if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
- checkExpiredDelaysAndResetAlarm();
- } else {
- final boolean isAlarmForJob =
- job.getEarliestRunTime() == mNextDelayExpiredElapsedMillis;
- final boolean wouldBeReady = wouldBeReadyWithConstraintLocked(
- job, JobStatus.CONSTRAINT_TIMING_DELAY);
- if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) {
- checkExpiredDelaysAndResetAlarm();
- }
- }
- }
- }
-
- @Override
- public void reevaluateStateLocked(int uid) {
- checkExpiredDeadlinesAndResetAlarm();
- checkExpiredDelaysAndResetAlarm();
- }
-
- /**
- * Determines whether this controller can stop tracking the given job.
- * The controller is no longer interested in a job once its time constraint is satisfied, and
- * the job's deadline is fulfilled - unlike other controllers a time constraint can't toggle
- * back and forth.
- */
- private boolean canStopTrackingJobLocked(JobStatus job) {
- return (!job.hasTimingDelayConstraint()
- || job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY))
- && (!job.hasDeadlineConstraint()
- || job.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
- }
-
- private void ensureAlarmServiceLocked() {
- if (mAlarmService == null) {
- mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- }
- }
-
- /**
- * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
- * if so, removing them from this list, and updating the alarm for the next expiry time.
- */
- @VisibleForTesting
- void checkExpiredDeadlinesAndResetAlarm() {
- synchronized (mLock) {
- long nextExpiryTime = Long.MAX_VALUE;
- int nextExpiryUid = 0;
- String nextExpiryPackageName = null;
- final long nowElapsedMillis = sElapsedRealtimeClock.millis();
-
- ListIterator<JobStatus> it = mTrackedJobs.listIterator();
- while (it.hasNext()) {
- JobStatus job = it.next();
- if (!job.hasDeadlineConstraint()) {
- continue;
- }
-
- if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
- if (job.isReady()) {
- // If the job still isn't ready, there's no point trying to rush the
- // Scheduler.
- mStateChangedListener.onRunJobNow(job);
- }
- it.remove();
- } else { // Sorted by expiry time, so take the next one and stop.
- if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
- if (DEBUG) {
- Slog.i(TAG,
- "Skipping " + job + " because deadline won't make it ready.");
- }
- continue;
- }
- nextExpiryTime = job.getLatestRunTimeElapsed();
- nextExpiryUid = job.getSourceUid();
- nextExpiryPackageName = job.getSourcePackageName();
- break;
- }
- }
- setDeadlineExpiredAlarmLocked(nextExpiryTime,
- deriveWorkSource(nextExpiryUid, nextExpiryPackageName));
- }
- }
-
- /** @return true if the job's deadline constraint is satisfied */
- private boolean evaluateDeadlineConstraint(JobStatus job, long nowElapsedMillis) {
- final long jobDeadline = job.getLatestRunTimeElapsed();
-
- if (jobDeadline <= nowElapsedMillis) {
- if (job.hasTimingDelayConstraint()) {
- job.setTimingDelayConstraintSatisfied(true);
- }
- job.setDeadlineConstraintSatisfied(true);
- return true;
- }
- return false;
- }
-
- /**
- * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
- * tracked jobs and marks them as ready as appropriate.
- */
- @VisibleForTesting
- void checkExpiredDelaysAndResetAlarm() {
- synchronized (mLock) {
- final long nowElapsedMillis = sElapsedRealtimeClock.millis();
- long nextDelayTime = Long.MAX_VALUE;
- int nextDelayUid = 0;
- String nextDelayPackageName = null;
- boolean ready = false;
- Iterator<JobStatus> it = mTrackedJobs.iterator();
- while (it.hasNext()) {
- final JobStatus job = it.next();
- if (!job.hasTimingDelayConstraint()) {
- continue;
- }
- if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
- if (canStopTrackingJobLocked(job)) {
- it.remove();
- }
- if (job.isReady()) {
- ready = true;
- }
- } else {
- if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
- if (DEBUG) {
- Slog.i(TAG,
- "Skipping " + job + " because delay won't make it ready.");
- }
- continue;
- }
- // If this job still doesn't have its delay constraint satisfied,
- // then see if it is the next upcoming delay time for the alarm.
- final long jobDelayTime = job.getEarliestRunTime();
- if (nextDelayTime > jobDelayTime) {
- nextDelayTime = jobDelayTime;
- nextDelayUid = job.getSourceUid();
- nextDelayPackageName = job.getSourcePackageName();
- }
- }
- }
- if (ready) {
- mStateChangedListener.onControllerStateChanged();
- }
- setDelayExpiredAlarmLocked(nextDelayTime,
- deriveWorkSource(nextDelayUid, nextDelayPackageName));
- }
- }
-
- private WorkSource deriveWorkSource(int uid, @Nullable String packageName) {
- if (mChainedAttributionEnabled) {
- WorkSource ws = new WorkSource();
- ws.createWorkChain()
- .addNode(uid, packageName)
- .addNode(Process.SYSTEM_UID, "JobScheduler");
- return ws;
- } else {
- return packageName == null ? new WorkSource(uid) : new WorkSource(uid, packageName);
- }
- }
-
- /** @return true if the job's delay constraint is satisfied */
- private boolean evaluateTimingDelayConstraint(JobStatus job, long nowElapsedMillis) {
- final long jobDelayTime = job.getEarliestRunTime();
- if (jobDelayTime <= nowElapsedMillis) {
- job.setTimingDelayConstraintSatisfied(true);
- return true;
- }
- return false;
- }
-
- private void maybeUpdateDelayAlarmLocked(long delayExpiredElapsed, WorkSource ws) {
- if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
- setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
- }
- }
-
- private void maybeUpdateDeadlineAlarmLocked(long deadlineExpiredElapsed, WorkSource ws) {
- if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
- setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
- }
- }
-
- /**
- * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
- * delay will expire.
- * This alarm <b>will</b> wake up the phone.
- */
- private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
- alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
- if (mNextDelayExpiredElapsedMillis == alarmTimeElapsedMillis) {
- return;
- }
- mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
- updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
- mNextDelayExpiredElapsedMillis, ws);
- }
-
- /**
- * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
- * deadline will expire.
- * This alarm <b>will</b> wake up the phone.
- */
- private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
- alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
- if (mNextJobExpiredElapsedMillis == alarmTimeElapsedMillis) {
- return;
- }
- mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
- updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
- mNextJobExpiredElapsedMillis, ws);
- }
-
- private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
- return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis());
- }
-
- private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
- long alarmTimeElapsed, WorkSource ws) {
- ensureAlarmServiceLocked();
- if (alarmTimeElapsed == Long.MAX_VALUE) {
- mAlarmService.cancel(listener);
- } else {
- if (DEBUG) {
- Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
- }
- mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
- AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
- }
- }
-
- // Job/delay expiration alarm handling
-
- private final OnAlarmListener mDeadlineExpiredListener = new OnAlarmListener() {
- @Override
- public void onAlarm() {
- if (DEBUG) {
- Slog.d(TAG, "Deadline-expired alarm fired");
- }
- checkExpiredDeadlinesAndResetAlarm();
- }
- };
-
- private final OnAlarmListener mNextDelayExpiredListener = new OnAlarmListener() {
- @Override
- public void onAlarm() {
- if (DEBUG) {
- Slog.d(TAG, "Delay-expired alarm fired");
- }
- checkExpiredDelaysAndResetAlarm();
- }
- };
-
- @VisibleForTesting
- void recheckAlarmsLocked() {
- checkExpiredDeadlinesAndResetAlarm();
- checkExpiredDelaysAndResetAlarm();
- }
-
- @Override
- public void dumpControllerStateLocked(IndentingPrintWriter pw,
- Predicate<JobStatus> predicate) {
- final long nowElapsed = sElapsedRealtimeClock.millis();
- pw.println("Elapsed clock: " + nowElapsed);
-
- pw.print("Next delay alarm in ");
- TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
- pw.println();
- pw.print("Next deadline alarm in ");
- TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw);
- pw.println();
- pw.println();
-
- for (JobStatus ts : mTrackedJobs) {
- if (!predicate.test(ts)) {
- continue;
- }
- pw.print("#");
- ts.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, ts.getSourceUid());
- pw.print(": Delay=");
- if (ts.hasTimingDelayConstraint()) {
- TimeUtils.formatDuration(ts.getEarliestRunTime(), nowElapsed, pw);
- } else {
- pw.print("N/A");
- }
- pw.print(", Deadline=");
- if (ts.hasDeadlineConstraint()) {
- TimeUtils.formatDuration(ts.getLatestRunTimeElapsed(), nowElapsed, pw);
- } else {
- pw.print("N/A");
- }
- 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.TIME);
-
- final long nowElapsed = sElapsedRealtimeClock.millis();
- proto.write(StateControllerProto.TimeController.NOW_ELAPSED_REALTIME, nowElapsed);
- proto.write(StateControllerProto.TimeController.TIME_UNTIL_NEXT_DELAY_ALARM_MS,
- mNextDelayExpiredElapsedMillis - nowElapsed);
- proto.write(StateControllerProto.TimeController.TIME_UNTIL_NEXT_DEADLINE_ALARM_MS,
- mNextJobExpiredElapsedMillis - nowElapsed);
-
- for (JobStatus ts : mTrackedJobs) {
- if (!predicate.test(ts)) {
- continue;
- }
- final long tsToken = proto.start(StateControllerProto.TimeController.TRACKED_JOBS);
- ts.writeToShortProto(proto, StateControllerProto.TimeController.TrackedJob.INFO);
-
- proto.write(StateControllerProto.TimeController.TrackedJob.HAS_TIMING_DELAY_CONSTRAINT,
- ts.hasTimingDelayConstraint());
- proto.write(StateControllerProto.TimeController.TrackedJob.DELAY_TIME_REMAINING_MS,
- ts.getEarliestRunTime() - nowElapsed);
-
- proto.write(StateControllerProto.TimeController.TrackedJob.HAS_DEADLINE_CONSTRAINT,
- ts.hasDeadlineConstraint());
- proto.write(StateControllerProto.TimeController.TrackedJob.TIME_REMAINING_UNTIL_DEADLINE_MS,
- ts.getLatestRunTimeElapsed() - nowElapsed);
-
- proto.end(tsToken);
- }
-
- proto.end(mToken);
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
deleted file mode 100644
index 82c33f5..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 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.idle;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.server.am.ActivityManagerService;
-import com.android.server.job.JobSchedulerService;
-
-import java.io.PrintWriter;
-
-public final class CarIdlenessTracker extends BroadcastReceiver implements IdlenessTracker {
- private static final String TAG = "JobScheduler.CarIdlenessTracker";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- public static final String ACTION_GARAGE_MODE_ON =
- "com.android.server.jobscheduler.GARAGE_MODE_ON";
- public static final String ACTION_GARAGE_MODE_OFF =
- "com.android.server.jobscheduler.GARAGE_MODE_OFF";
-
- public static final String ACTION_FORCE_IDLE = "com.android.server.jobscheduler.FORCE_IDLE";
- public static final String ACTION_UNFORCE_IDLE = "com.android.server.jobscheduler.UNFORCE_IDLE";
-
- // After construction, mutations of idle/screen-on state will only happen
- // on the main looper thread, either in onReceive() or in an alarm callback.
- private boolean mIdle;
- private boolean mGarageModeOn;
- private boolean mForced;
- private IdlenessListener mIdleListener;
-
- public CarIdlenessTracker() {
- // At boot we presume that the user has just "interacted" with the
- // device in some meaningful way.
- mIdle = false;
- mGarageModeOn = false;
- mForced = false;
- }
-
- @Override
- public boolean isIdle() {
- return mIdle;
- }
-
- @Override
- public void startTracking(Context context, IdlenessListener listener) {
- mIdleListener = listener;
-
- IntentFilter filter = new IntentFilter();
-
- // Screen state
- filter.addAction(Intent.ACTION_SCREEN_ON);
-
- // State of GarageMode
- filter.addAction(ACTION_GARAGE_MODE_ON);
- filter.addAction(ACTION_GARAGE_MODE_OFF);
-
- // Debugging/instrumentation
- filter.addAction(ACTION_FORCE_IDLE);
- filter.addAction(ACTION_UNFORCE_IDLE);
- filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);
-
- context.registerReceiver(this, filter);
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.print(" mIdle: "); pw.println(mIdle);
- pw.print(" mGarageModeOn: "); pw.println(mGarageModeOn);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- logIfDebug("Received action: " + action);
-
- // Check for forced actions
- if (action.equals(ACTION_FORCE_IDLE)) {
- logIfDebug("Forcing idle...");
- setForceIdleState(true);
- } else if (action.equals(ACTION_UNFORCE_IDLE)) {
- logIfDebug("Unforcing idle...");
- setForceIdleState(false);
- } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
- logIfDebug("Screen is on...");
- handleScreenOn();
- } else if (action.equals(ACTION_GARAGE_MODE_ON)) {
- logIfDebug("GarageMode is on...");
- mGarageModeOn = true;
- updateIdlenessState();
- } else if (action.equals(ACTION_GARAGE_MODE_OFF)) {
- logIfDebug("GarageMode is off...");
- mGarageModeOn = false;
- updateIdlenessState();
- } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
- if (!mGarageModeOn) {
- logIfDebug("Idle trigger fired...");
- triggerIdlenessOnce();
- } else {
- logIfDebug("TRIGGER_IDLE received but not changing state; idle="
- + mIdle + " screen=" + mGarageModeOn);
- }
- }
- }
-
- private void setForceIdleState(boolean forced) {
- mForced = forced;
- updateIdlenessState();
- }
-
- private void updateIdlenessState() {
- final boolean newState = (mForced || mGarageModeOn);
- if (mIdle != newState) {
- // State of idleness changed. Notifying idleness controller
- logIfDebug("Device idleness changed. New idle=" + newState);
- mIdle = newState;
- mIdleListener.reportNewIdleState(mIdle);
- } else {
- // Nothing changed, device idleness is in the same state as new state
- logIfDebug("Device idleness is the same. Current idle=" + newState);
- }
- }
-
- private void triggerIdlenessOnce() {
- // This is simply triggering idleness once until some constraint will switch it back off
- if (mIdle) {
- // Already in idle state. Nothing to do
- logIfDebug("Device is already idle");
- } else {
- // Going idle once
- logIfDebug("Device is going idle once");
- mIdle = true;
- mIdleListener.reportNewIdleState(mIdle);
- }
- }
-
- private void handleScreenOn() {
- if (mForced || mGarageModeOn) {
- // Even though screen is on, the device remains idle
- logIfDebug("Screen is on, but device cannot exit idle");
- } else if (mIdle) {
- // Exiting idle
- logIfDebug("Device is exiting idle");
- mIdle = false;
- } else {
- // Already in non-idle state. Nothing to do
- logIfDebug("Device is already non-idle");
- }
- }
-
- private static void logIfDebug(String msg) {
- if (DEBUG) {
- Slog.v(TAG, msg);
- }
- }
-}
diff --git a/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
deleted file mode 100644
index a85bd40..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 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.idle;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-
-import android.util.Log;
-import android.util.Slog;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.job.JobSchedulerService;
-
-import java.io.PrintWriter;
-
-public final class DeviceIdlenessTracker extends BroadcastReceiver implements IdlenessTracker {
- private static final String TAG = "JobScheduler.DeviceIdlenessTracker";
- private static final boolean DEBUG = JobSchedulerService.DEBUG
- || Log.isLoggable(TAG, Log.DEBUG);
-
- private AlarmManager mAlarm;
-
- // After construction, mutations of idle/screen-on state will only happen
- // on the main looper thread, either in onReceive() or in an alarm callback.
- private long mInactivityIdleThreshold;
- private long mIdleWindowSlop;
- private boolean mIdle;
- private boolean mScreenOn;
- private boolean mDockIdle;
- private IdlenessListener mIdleListener;
-
- private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> {
- handleIdleTrigger();
- };
-
- public DeviceIdlenessTracker() {
- // At boot we presume that the user has just "interacted" with the
- // device in some meaningful way.
- mIdle = false;
- mScreenOn = true;
- mDockIdle = false;
- }
-
- @Override
- public boolean isIdle() {
- return mIdle;
- }
-
- @Override
- public void startTracking(Context context, IdlenessListener listener) {
- mIdleListener = listener;
- mInactivityIdleThreshold = context.getResources().getInteger(
- com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
- mIdleWindowSlop = context.getResources().getInteger(
- com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
- mAlarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
- IntentFilter filter = new IntentFilter();
-
- // Screen state
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
-
- // Dreaming state
- filter.addAction(Intent.ACTION_DREAMING_STARTED);
- filter.addAction(Intent.ACTION_DREAMING_STOPPED);
-
- // Debugging/instrumentation
- filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);
-
- // Wireless charging dock state
- filter.addAction(Intent.ACTION_DOCK_IDLE);
- filter.addAction(Intent.ACTION_DOCK_ACTIVE);
-
- context.registerReceiver(this, filter);
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.print(" mIdle: "); pw.println(mIdle);
- pw.print(" mScreenOn: "); pw.println(mScreenOn);
- pw.print(" mDockIdle: "); pw.println(mDockIdle);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(Intent.ACTION_SCREEN_ON)
- || action.equals(Intent.ACTION_DREAMING_STOPPED)
- || action.equals(Intent.ACTION_DOCK_ACTIVE)) {
- if (action.equals(Intent.ACTION_DOCK_ACTIVE)) {
- if (!mScreenOn) {
- // Ignore this intent during screen off
- return;
- } else {
- mDockIdle = false;
- }
- } else {
- mScreenOn = true;
- mDockIdle = false;
- }
- if (DEBUG) {
- Slog.v(TAG,"exiting idle : " + action);
- }
- //cancel the alarm
- mAlarm.cancel(mIdleAlarmListener);
- if (mIdle) {
- // possible transition to not-idle
- mIdle = false;
- mIdleListener.reportNewIdleState(mIdle);
- }
- } else if (action.equals(Intent.ACTION_SCREEN_OFF)
- || action.equals(Intent.ACTION_DREAMING_STARTED)
- || action.equals(Intent.ACTION_DOCK_IDLE)) {
- // when the screen goes off or dreaming starts or wireless charging dock in idle,
- // we schedule the alarm that will tell us when we have decided the device is
- // truly idle.
- if (action.equals(Intent.ACTION_DOCK_IDLE)) {
- if (!mScreenOn) {
- // Ignore this intent during screen off
- return;
- } else {
- mDockIdle = true;
- }
- } else {
- mScreenOn = false;
- mDockIdle = false;
- }
- final long nowElapsed = sElapsedRealtimeClock.millis();
- final long when = nowElapsed + mInactivityIdleThreshold;
- if (DEBUG) {
- Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
- + when);
- }
- mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null);
- } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
- handleIdleTrigger();
- }
- }
-
- private void handleIdleTrigger() {
- // idle time starts now. Do not set mIdle if screen is on.
- if (!mIdle && (!mScreenOn || mDockIdle)) {
- if (DEBUG) {
- Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
- }
- mIdle = true;
- mIdleListener.reportNewIdleState(mIdle);
- } else {
- if (DEBUG) {
- Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle="
- + mIdle + " screen=" + mScreenOn);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java
deleted file mode 100644
index 7ffd7cd..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2018 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.idle;
-
-/**
- * Interface through which an IdlenessTracker informs the job scheduler of
- * changes in the device's inactivity state.
- */
-public interface IdlenessListener {
- /**
- * Tell the job scheduler that the device's idle state has changed.
- *
- * @param deviceIsIdle {@code true} to indicate that the device is now considered
- * to be idle; {@code false} to indicate that the device is now being interacted with,
- * so jobs with idle constraints should not be run.
- */
- void reportNewIdleState(boolean deviceIsIdle);
-}
diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java
deleted file mode 100644
index 09f01c2..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 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.idle;
-
-import android.content.Context;
-
-import java.io.PrintWriter;
-
-public interface IdlenessTracker {
- /**
- * One-time initialization: this method is called once, after construction of
- * the IdlenessTracker instance. This is when the tracker should actually begin
- * monitoring whatever signals it consumes in deciding when the device is in a
- * non-interacting state. When the idle state changes thereafter, the given
- * listener must be called to report the new state.
- */
- void startTracking(Context context, IdlenessListener listener);
-
- /**
- * Report whether the device is currently considered "idle" for purposes of
- * running scheduled jobs with idleness constraints.
- *
- * @return {@code true} if the job scheduler should consider idleness
- * constraints to be currently satisfied; {@code false} otherwise.
- */
- boolean isIdle();
-
- /**
- * Dump useful information about tracked idleness-related state in plaintext.
- */
- void dump(PrintWriter pw);
-}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9fe44dc..8161ac9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -102,7 +102,6 @@
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.inputmethod.MultiClientInputMethodManagerService;
-import com.android.server.job.JobSchedulerService;
import com.android.server.lights.LightsService;
import com.android.server.media.MediaResourceMonitorService;
import com.android.server.media.MediaRouterService;
@@ -1575,8 +1574,9 @@
mSystemServiceManager.startService(ColorDisplayService.class);
t.traceEnd();
+ // TODO(aml-jobscheduler): Think about how to do it properly.
t.traceBegin("StartJobScheduler");
- mSystemServiceManager.startService(JobSchedulerService.class);
+ mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS);
t.traceEnd();
t.traceBegin("StartSoundTrigger");
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 2baa4d8..ad94e61 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -20,6 +20,7 @@
static_libs: [
"services.core",
"services.net",
+ "jobscheduler-service",
"androidx.test.runner",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0614b3a..b37e460 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -40,6 +40,7 @@
"hamcrest-library",
"servicestests-utils",
"xml-writer-device-lib",
+ "jobscheduler-service",
],
aidl: {