blob: 2dbecbd4f51565f5cff0a601a2dbceb592b22524 [file] [log] [blame]
/*
* Copyright (C) 2015 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.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.os.UserHandle;
import android.util.Slog;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
import java.io.PrintWriter;
/**
* Controls when apps are considered idle and if jobs pertaining to those apps should
* be executed. Apps that haven't been actively launched or accessed from a foreground app
* for a certain amount of time (maybe hours or days) are considered idle. When the app comes
* out of idle state, it will be allowed to run scheduled jobs.
*/
public class AppIdleController extends StateController {
private static final String LOG_TAG = "AppIdleController";
private static final boolean DEBUG = false;
// Singleton factory
private static Object sCreationLock = new Object();
private static volatile AppIdleController sController;
private final JobSchedulerService mJobSchedulerService;
private final UsageStatsManagerInternal mUsageStatsInternal;
private boolean mInitializedParoleOn;
boolean mAppIdleParoleOn;
final class GlobalUpdateFunc implements JobStore.JobStatusFunctor {
boolean mChanged;
@Override public void process(JobStatus jobStatus) {
String packageName = jobStatus.getSourcePackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
jobStatus.getSourceUid(), jobStatus.getSourceUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
}
if (jobStatus.setAppNotIdleConstraintSatisfied(!appIdle)) {
mChanged = true;
}
}
};
final static class PackageUpdateFunc implements JobStore.JobStatusFunctor {
final int mUserId;
final String mPackage;
final boolean mIdle;
boolean mChanged;
PackageUpdateFunc(int userId, String pkg, boolean idle) {
mUserId = userId;
mPackage = pkg;
mIdle = idle;
}
@Override public void process(JobStatus jobStatus) {
if (jobStatus.getSourcePackageName().equals(mPackage)
&& jobStatus.getSourceUserId() == mUserId) {
if (jobStatus.setAppNotIdleConstraintSatisfied(!mIdle)) {
if (DEBUG) {
Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
+ mPackage + " to " + mIdle);
}
mChanged = true;
}
}
}
};
public static AppIdleController get(JobSchedulerService service) {
synchronized (sCreationLock) {
if (sController == null) {
sController = new AppIdleController(service, service.getContext(),
service.getLock());
}
return sController;
}
}
private AppIdleController(JobSchedulerService service, Context context, Object lock) {
super(service, context, lock);
mJobSchedulerService = service;
mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
mAppIdleParoleOn = true;
mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (!mInitializedParoleOn) {
mInitializedParoleOn = true;
mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
}
String packageName = jobStatus.getSourcePackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
jobStatus.getSourceUid(), jobStatus.getSourceUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Start tracking, setting idle state of "
+ packageName + " to " + appIdle);
}
jobStatus.setAppNotIdleConstraintSatisfied(!appIdle);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
}
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.print("AppIdle: parole on = ");
pw.println(mAppIdleParoleOn);
mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
@Override public void process(JobStatus jobStatus) {
// Skip printing details if the caller requested a filter
if (!jobStatus.shouldDump(filterUid)) {
return;
}
pw.print(" #");
jobStatus.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, jobStatus.getSourceUid());
pw.print(": ");
pw.print(jobStatus.getSourcePackageName());
if ((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0) {
pw.println(" RUNNABLE");
} else {
pw.println(" WAITING");
}
}
});
}
void setAppIdleParoleOn(boolean isAppIdleParoleOn) {
// Flag if any app's idle state has changed
boolean changed = false;
synchronized (mLock) {
if (mAppIdleParoleOn == isAppIdleParoleOn) {
return;
}
mAppIdleParoleOn = isAppIdleParoleOn;
GlobalUpdateFunc update = new GlobalUpdateFunc();
mJobSchedulerService.getJobStore().forEachJob(update);
if (update.mChanged) {
changed = true;
}
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
}
}
private class AppIdleStateChangeListener
extends UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
boolean changed = false;
synchronized (mLock) {
if (mAppIdleParoleOn) {
return;
}
PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
mJobSchedulerService.getJobStore().forEachJob(update);
if (update.mChanged) {
changed = true;
}
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
}
}
@Override
public void onParoleStateChanged(boolean isParoleOn) {
if (DEBUG) {
Slog.d(LOG_TAG, "Parole on: " + isParoleOn);
}
setAppIdleParoleOn(isParoleOn);
}
}
}