AppIdleController.java revision 6bfe557743396e7f88d4047798e31604dacd1e1e
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.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.os.BatteryManager; 25import android.os.BatteryManagerInternal; 26import android.util.Slog; 27 28import com.android.server.LocalServices; 29import com.android.server.job.JobSchedulerService; 30import com.android.server.job.StateChangedListener; 31 32import java.io.PrintWriter; 33import java.util.ArrayList; 34 35/** 36 * Controls when apps are considered idle and if jobs pertaining to those apps should 37 * be executed. Apps that haven't been actively launched or accessed from a foreground app 38 * for a certain amount of time (maybe hours or days) are considered idle. When the app comes 39 * out of idle state, it will be allowed to run scheduled jobs. 40 */ 41public class AppIdleController extends StateController 42 implements UsageStatsManagerInternal.AppIdleStateChangeListener { 43 44 private static final String LOG_TAG = "AppIdleController"; 45 private static final boolean DEBUG = false; 46 47 // Singleton factory 48 private static Object sCreationLock = new Object(); 49 private static volatile AppIdleController sController; 50 final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); 51 private final UsageStatsManagerInternal mUsageStatsInternal; 52 private final BatteryManagerInternal mBatteryManagerInternal; 53 private boolean mPluggedIn; 54 55 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 56 @Override public void onReceive(Context context, Intent intent) { 57 if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { 58 int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); 59 // TODO: Allow any charger type 60 onPluggedIn((plugged & BatteryManager.BATTERY_PLUGGED_AC) != 0); 61 } 62 } 63 }; 64 65 public static AppIdleController get(JobSchedulerService service) { 66 synchronized (sCreationLock) { 67 if (sController == null) { 68 sController = new AppIdleController(service, service.getContext()); 69 } 70 return sController; 71 } 72 } 73 74 private AppIdleController(StateChangedListener stateChangedListener, Context context) { 75 super(stateChangedListener, context); 76 mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class); 77 mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); 78 mPluggedIn = isPowered(); 79 mUsageStatsInternal.addAppIdleStateChangeListener(this); 80 registerReceivers(); 81 } 82 83 private void registerReceivers() { 84 // Monitor battery charging state 85 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 86 mContext.registerReceiver(mReceiver, filter); 87 } 88 89 private boolean isPowered() { 90 // TODO: Allow any charger type 91 return mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_AC); 92 } 93 94 @Override 95 public void maybeStartTrackingJob(JobStatus jobStatus) { 96 synchronized (mTrackedTasks) { 97 mTrackedTasks.add(jobStatus); 98 String packageName = jobStatus.job.getService().getPackageName(); 99 final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, 100 jobStatus.getUserId()); 101 if (DEBUG) { 102 Slog.d(LOG_TAG, "Start tracking, setting idle state of " 103 + packageName + " to " + appIdle); 104 } 105 jobStatus.appNotIdleConstraintSatisfied.set(!appIdle); 106 } 107 } 108 109 @Override 110 public void maybeStopTrackingJob(JobStatus jobStatus) { 111 synchronized (mTrackedTasks) { 112 mTrackedTasks.remove(jobStatus); 113 } 114 } 115 116 @Override 117 public void dumpControllerState(PrintWriter pw) { 118 pw.println("AppIdle"); 119 pw.println("Plugged In: " + mPluggedIn); 120 synchronized (mTrackedTasks) { 121 for (JobStatus task : mTrackedTasks) { 122 pw.print(task.job.getService().getPackageName()); 123 pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get()); 124 pw.print(", "); 125 } 126 pw.println(); 127 } 128 } 129 130 @Override 131 public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { 132 boolean changed = false; 133 synchronized (mTrackedTasks) { 134 // If currently plugged in, we don't care about app idle state 135 if (mPluggedIn) { 136 return; 137 } 138 for (JobStatus task : mTrackedTasks) { 139 if (task.job.getService().getPackageName().equals(packageName) 140 && task.getUserId() == userId) { 141 if (task.appNotIdleConstraintSatisfied.get() != !idle) { 142 if (DEBUG) { 143 Slog.d(LOG_TAG, "App Idle state changed, setting idle state of " 144 + packageName + " to " + idle); 145 } 146 task.appNotIdleConstraintSatisfied.set(!idle); 147 changed = true; 148 } 149 } 150 } 151 } 152 if (changed) { 153 mStateChangedListener.onControllerStateChanged(); 154 } 155 } 156 157 void onPluggedIn(boolean pluggedIn) { 158 // Flag if any app's idle state has changed 159 boolean changed = false; 160 synchronized (mTrackedTasks) { 161 if (mPluggedIn == pluggedIn) { 162 return; 163 } 164 mPluggedIn = pluggedIn; 165 for (JobStatus task : mTrackedTasks) { 166 String packageName = task.job.getService().getPackageName(); 167 final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, 168 task.getUserId()); 169 if (DEBUG) { 170 Slog.d(LOG_TAG, "Plugged in " + pluggedIn + ", setting idle state of " 171 + packageName + " to " + appIdle); 172 } 173 if (task.appNotIdleConstraintSatisfied.get() == appIdle) { 174 task.appNotIdleConstraintSatisfied.set(!appIdle); 175 changed = true; 176 } 177 } 178 } 179 if (changed) { 180 mStateChangedListener.onControllerStateChanged(); 181 } 182 } 183} 184