AppIdleController.java revision e9a988caca733d2f292991a52a0047685a69812f
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.server.job.controllers; 18 19import android.app.usage.UsageStatsManagerInternal; 20import android.content.Context; 21import android.os.UserHandle; 22import android.util.Slog; 23 24import com.android.server.LocalServices; 25import com.android.server.job.JobSchedulerService; 26import com.android.server.job.JobStore; 27 28import java.io.PrintWriter; 29 30/** 31 * Controls when apps are considered idle and if jobs pertaining to those apps should 32 * be executed. Apps that haven't been actively launched or accessed from a foreground app 33 * for a certain amount of time (maybe hours or days) are considered idle. When the app comes 34 * out of idle state, it will be allowed to run scheduled jobs. 35 */ 36public class AppIdleController extends StateController { 37 38 private static final String LOG_TAG = "AppIdleController"; 39 private static final boolean DEBUG = false; 40 41 // Singleton factory 42 private static Object sCreationLock = new Object(); 43 private static volatile AppIdleController sController; 44 private final JobSchedulerService mJobSchedulerService; 45 private final UsageStatsManagerInternal mUsageStatsInternal; 46 private boolean mInitializedParoleOn; 47 boolean mAppIdleParoleOn; 48 49 final class GlobalUpdateFunc implements JobStore.JobStatusFunctor { 50 boolean mChanged; 51 52 @Override public void process(JobStatus jobStatus) { 53 String packageName = jobStatus.getSourcePackageName(); 54 final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName, 55 jobStatus.getSourceUid(), jobStatus.getSourceUserId()); 56 if (DEBUG) { 57 Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle); 58 } 59 if (jobStatus.setAppNotIdleConstraintSatisfied(!appIdle)) { 60 mChanged = true; 61 } 62 } 63 }; 64 65 final static class PackageUpdateFunc implements JobStore.JobStatusFunctor { 66 final int mUserId; 67 final String mPackage; 68 final boolean mIdle; 69 boolean mChanged; 70 71 PackageUpdateFunc(int userId, String pkg, boolean idle) { 72 mUserId = userId; 73 mPackage = pkg; 74 mIdle = idle; 75 } 76 77 @Override public void process(JobStatus jobStatus) { 78 if (jobStatus.getSourcePackageName().equals(mPackage) 79 && jobStatus.getSourceUserId() == mUserId) { 80 if (jobStatus.setAppNotIdleConstraintSatisfied(!mIdle)) { 81 if (DEBUG) { 82 Slog.d(LOG_TAG, "App Idle state changed, setting idle state of " 83 + mPackage + " to " + mIdle); 84 } 85 mChanged = true; 86 } 87 } 88 } 89 }; 90 91 public static AppIdleController get(JobSchedulerService service) { 92 synchronized (sCreationLock) { 93 if (sController == null) { 94 sController = new AppIdleController(service, service.getContext(), 95 service.getLock()); 96 } 97 return sController; 98 } 99 } 100 101 private AppIdleController(JobSchedulerService service, Context context, Object lock) { 102 super(service, context, lock); 103 mJobSchedulerService = service; 104 mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class); 105 mAppIdleParoleOn = true; 106 mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener()); 107 } 108 109 @Override 110 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 111 if (!mInitializedParoleOn) { 112 mInitializedParoleOn = true; 113 mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn(); 114 } 115 String packageName = jobStatus.getSourcePackageName(); 116 final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName, 117 jobStatus.getSourceUid(), jobStatus.getSourceUserId()); 118 if (DEBUG) { 119 Slog.d(LOG_TAG, "Start tracking, setting idle state of " 120 + packageName + " to " + appIdle); 121 } 122 jobStatus.setAppNotIdleConstraintSatisfied(!appIdle); 123 } 124 125 @Override 126 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { 127 } 128 129 @Override 130 public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { 131 pw.print("AppIdle: parole on = "); 132 pw.println(mAppIdleParoleOn); 133 mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { 134 @Override public void process(JobStatus jobStatus) { 135 // Skip printing details if the caller requested a filter 136 if (!jobStatus.shouldDump(filterUid)) { 137 return; 138 } 139 pw.print(" #"); 140 jobStatus.printUniqueId(pw); 141 pw.print(" from "); 142 UserHandle.formatUid(pw, jobStatus.getSourceUid()); 143 pw.print(": "); 144 pw.print(jobStatus.getSourcePackageName()); 145 pw.print(", runnable="); 146 pw.println((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0); 147 } 148 }); 149 } 150 151 void setAppIdleParoleOn(boolean isAppIdleParoleOn) { 152 // Flag if any app's idle state has changed 153 boolean changed = false; 154 synchronized (mLock) { 155 if (mAppIdleParoleOn == isAppIdleParoleOn) { 156 return; 157 } 158 mAppIdleParoleOn = isAppIdleParoleOn; 159 GlobalUpdateFunc update = new GlobalUpdateFunc(); 160 mJobSchedulerService.getJobStore().forEachJob(update); 161 if (update.mChanged) { 162 changed = true; 163 } 164 } 165 if (changed) { 166 mStateChangedListener.onControllerStateChanged(); 167 } 168 } 169 170 private class AppIdleStateChangeListener 171 extends UsageStatsManagerInternal.AppIdleStateChangeListener { 172 @Override 173 public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { 174 boolean changed = false; 175 synchronized (mLock) { 176 if (mAppIdleParoleOn) { 177 return; 178 } 179 PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle); 180 mJobSchedulerService.getJobStore().forEachJob(update); 181 if (update.mChanged) { 182 changed = true; 183 } 184 } 185 if (changed) { 186 mStateChangedListener.onControllerStateChanged(); 187 } 188 } 189 190 @Override 191 public void onParoleStateChanged(boolean isParoleOn) { 192 if (DEBUG) { 193 Slog.d(LOG_TAG, "Parole on: " + isParoleOn); 194 } 195 setAppIdleParoleOn(isParoleOn); 196 } 197 } 198} 199