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.util.Slog; 22 23 import com.android.server.LocalServices; 24 import com.android.server.job.JobSchedulerService; 25 import com.android.server.job.StateChangedListener; 26 27 import java.io.PrintWriter; 28 import java.util.ArrayList; 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 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 final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); 45 private final UsageStatsManagerInternal mUsageStatsInternal; 46 boolean mAppIdleParoleOn; 47 48 public static AppIdleController get(JobSchedulerService service) { 49 synchronized (sCreationLock) { 50 if (sController == null) { 51 sController = new AppIdleController(service, service.getContext()); 52 } 53 return sController; 54 } 55 } 56 57 private AppIdleController(StateChangedListener stateChangedListener, Context context) { 58 super(stateChangedListener, context); 59 mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class); 60 mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn(); 61 mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener()); 62 } 63 64 @Override 65 public void maybeStartTrackingJob(JobStatus jobStatus) { 66 synchronized (mTrackedTasks) { 67 mTrackedTasks.add(jobStatus); 68 String packageName = jobStatus.job.getService().getPackageName(); 69 final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName, 70 jobStatus.getUserId()); 71 if (DEBUG) { 72 Slog.d(LOG_TAG, "Start tracking, setting idle state of " 73 + packageName + " to " + appIdle); 74 } 75 jobStatus.appNotIdleConstraintSatisfied.set(!appIdle); 76 } 77 } 78 79 @Override 80 public void maybeStopTrackingJob(JobStatus jobStatus) { 81 synchronized (mTrackedTasks) { 82 mTrackedTasks.remove(jobStatus); 83 } 84 } 85 86 @Override 87 public void dumpControllerState(PrintWriter pw) { 88 pw.println("AppIdle"); 89 pw.println("Parole On: " + mAppIdleParoleOn); 90 synchronized (mTrackedTasks) { 91 for (JobStatus task : mTrackedTasks) { 92 pw.print(task.job.getService().getPackageName()); 93 pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get()); 94 pw.print(", "); 95 } 96 pw.println(); 97 } 98 } 99 100 void setAppIdleParoleOn(boolean isAppIdleParoleOn) { 101 // Flag if any app's idle state has changed 102 boolean changed = false; 103 synchronized (mTrackedTasks) { 104 if (mAppIdleParoleOn == isAppIdleParoleOn) { 105 return; 106 } 107 mAppIdleParoleOn = isAppIdleParoleOn; 108 for (JobStatus task : mTrackedTasks) { 109 String packageName = task.job.getService().getPackageName(); 110 final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName, 111 task.getUserId()); 112 if (DEBUG) { 113 Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle); 114 } 115 if (task.appNotIdleConstraintSatisfied.get() == appIdle) { 116 task.appNotIdleConstraintSatisfied.set(!appIdle); 117 changed = true; 118 } 119 } 120 } 121 if (changed) { 122 mStateChangedListener.onControllerStateChanged(); 123 } 124 } 125 126 private class AppIdleStateChangeListener 127 extends UsageStatsManagerInternal.AppIdleStateChangeListener { 128 @Override 129 public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { 130 boolean changed = false; 131 synchronized (mTrackedTasks) { 132 if (mAppIdleParoleOn) { 133 return; 134 } 135 for (JobStatus task : mTrackedTasks) { 136 if (task.job.getService().getPackageName().equals(packageName) 137 && task.getUserId() == userId) { 138 if (task.appNotIdleConstraintSatisfied.get() != !idle) { 139 if (DEBUG) { 140 Slog.d(LOG_TAG, "App Idle state changed, setting idle state of " 141 + packageName + " to " + idle); 142 } 143 task.appNotIdleConstraintSatisfied.set(!idle); 144 changed = true; 145 } 146 } 147 } 148 } 149 if (changed) { 150 mStateChangedListener.onControllerStateChanged(); 151 } 152 } 153 154 @Override 155 public void onParoleStateChanged(boolean isParoleOn) { 156 if (DEBUG) { 157 Slog.d(LOG_TAG, "Parole on: " + isParoleOn); 158 } 159 setAppIdleParoleOn(isParoleOn); 160 } 161 } 162 } 163