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 17 package com.android.server.job.controllers; 18 19 import android.app.usage.UsageStatsManagerInternal; 20 import android.content.Context; 21 import android.os.UserHandle; 22 import android.util.Slog; 23 24 import com.android.server.LocalServices; 25 import com.android.server.job.JobSchedulerService; 26 import com.android.server.job.JobStore; 27 28 import 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 */ 36 public final 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, 127 boolean forUpdate) { 128 } 129 130 @Override 131 public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { 132 pw.print("AppIdle: parole on = "); 133 pw.println(mAppIdleParoleOn); 134 mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { 135 @Override public void process(JobStatus jobStatus) { 136 // Skip printing details if the caller requested a filter 137 if (!jobStatus.shouldDump(filterUid)) { 138 return; 139 } 140 pw.print(" #"); 141 jobStatus.printUniqueId(pw); 142 pw.print(" from "); 143 UserHandle.formatUid(pw, jobStatus.getSourceUid()); 144 pw.print(": "); 145 pw.print(jobStatus.getSourcePackageName()); 146 if ((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0) { 147 pw.println(" RUNNABLE"); 148 } else { 149 pw.println(" WAITING"); 150 } 151 } 152 }); 153 } 154 155 void setAppIdleParoleOn(boolean isAppIdleParoleOn) { 156 // Flag if any app's idle state has changed 157 boolean changed = false; 158 synchronized (mLock) { 159 if (mAppIdleParoleOn == isAppIdleParoleOn) { 160 return; 161 } 162 mAppIdleParoleOn = isAppIdleParoleOn; 163 GlobalUpdateFunc update = new GlobalUpdateFunc(); 164 mJobSchedulerService.getJobStore().forEachJob(update); 165 if (update.mChanged) { 166 changed = true; 167 } 168 } 169 if (changed) { 170 mStateChangedListener.onControllerStateChanged(); 171 } 172 } 173 174 private final class AppIdleStateChangeListener 175 extends UsageStatsManagerInternal.AppIdleStateChangeListener { 176 @Override 177 public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { 178 boolean changed = false; 179 synchronized (mLock) { 180 if (mAppIdleParoleOn) { 181 return; 182 } 183 PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle); 184 mJobSchedulerService.getJobStore().forEachJob(update); 185 if (update.mChanged) { 186 changed = true; 187 } 188 } 189 if (changed) { 190 mStateChangedListener.onControllerStateChanged(); 191 } 192 } 193 194 @Override 195 public void onParoleStateChanged(boolean isParoleOn) { 196 if (DEBUG) { 197 Slog.d(LOG_TAG, "Parole on: " + isParoleOn); 198 } 199 setAppIdleParoleOn(isParoleOn); 200 } 201 } 202 } 203