diff options
Diffstat (limited to 'services/core')
6 files changed, 217 insertions, 6 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 7a74e45..c2b0a4d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -73,6 +73,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false; static final boolean DEBUG_USER_LEAVING = DEBUG_ALL || false; static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false; + static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || true; static final String POSTFIX_BACKUP = (APPEND_CATEGORY_NAME) ? "_Backup" : ""; static final String POSTFIX_BROADCAST = (APPEND_CATEGORY_NAME) ? "_Broadcast" : ""; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b0b410b..cdaa5a3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -43,6 +43,7 @@ import android.app.ITaskStackListener; import android.app.ProfilerInfo; import android.app.admin.DevicePolicyManager; import android.app.usage.UsageEvents; +import android.app.usage.UsageStats; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; import android.content.res.Resources; @@ -61,8 +62,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.SparseIntArray; - import android.view.Display; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.DumpHeapActivity; @@ -96,7 +97,6 @@ import com.android.server.pm.UserManagerService; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.AppTransition; import com.android.server.wm.WindowManagerService; - import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -17854,6 +17854,10 @@ public final class ActivityManagerService extends ActivityManagerNative app.lastCpuTime = app.curCpuTime; } + // Inform UsageStats of important process state change + // Must be called before updating setProcState + maybeUpdateUsageStats(app); + app.setProcState = app.curProcState; if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) { app.notCachedSinceIdle = false; @@ -17916,6 +17920,28 @@ public final class ActivityManagerService extends ActivityManagerNative return success; } + private void maybeUpdateUsageStats(ProcessRecord app) { + if (DEBUG_USAGE_STATS) { + Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList()) + + "] state changes: old = " + app.setProcState + ", new = " + + app.curProcState); + } + if (mUsageStatsService == null) { + return; + } + if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + && (app.setProcState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + || app.setProcState < 0)) { + String[] packages = app.getPackageList(); + if (packages != null) { + for (int i = 0; i < packages.length; i++) { + mUsageStatsService.reportEvent(packages[i], app.userId, + UsageEvents.Event.INTERACTION); + } + } + } + } + private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) { if (proc.thread != null) { if (proc.baseProcessTracker != null) { diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index d79b5fd..ecda36a 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -51,6 +51,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.app.IBatteryStats; +import com.android.server.job.controllers.AppIdleController; import com.android.server.job.controllers.BatteryController; import com.android.server.job.controllers.ConnectivityController; import com.android.server.job.controllers.IdleController; @@ -317,6 +318,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(TimeController.get(this)); mControllers.add(IdleController.get(this)); mControllers.add(BatteryController.get(this)); + mControllers.add(AppIdleController.get(this)); mHandler = new JobHandler(context.getMainLooper()); mJobSchedulerStub = new JobSchedulerStub(); @@ -688,7 +690,6 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean jobPending = mPendingJobs.contains(job); final boolean jobActive = isCurrentlyActiveLocked(job); final boolean userRunning = mStartedUsers.contains(job.getUserId()); - if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() + " ready=" + jobReady + " pending=" + jobPending @@ -738,6 +739,10 @@ public class JobSchedulerService extends com.android.server.SystemService } } if (availableContext != null) { + if (DEBUG) { + Slog.d(TAG, "About to run job " + + nextPending.getJob().getService().toString()); + } if (!availableContext.executeRunnableJob(nextPending)) { if (DEBUG) { Slog.d(TAG, "Error executing " + nextPending); diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java new file mode 100644 index 0000000..03e9ad5 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -0,0 +1,174 @@ +/* + * 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.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.StateChangedListener; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * 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 + implements UsageStatsManagerInternal.AppIdleStateChangeListener { + + private static final String LOG_TAG = "AppIdleController"; + private static final boolean DEBUG = true; + + // Singleton factory + private static Object sCreationLock = new Object(); + private static volatile AppIdleController sController; + final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); + private final UsageStatsManagerInternal mUsageStatsInternal; + private final BatteryManagerInternal mBatteryManagerInternal; + private boolean mPluggedIn; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); + // TODO: Allow any charger type + onPluggedIn((plugged & BatteryManager.BATTERY_PLUGGED_AC) != 0); + } + } + }; + + public static AppIdleController get(JobSchedulerService service) { + synchronized (sCreationLock) { + if (sController == null) { + sController = new AppIdleController(service, service.getContext()); + } + return sController; + } + } + + private AppIdleController(StateChangedListener stateChangedListener, Context context) { + super(stateChangedListener, context); + mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class); + mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); + mPluggedIn = isPowered(); + mUsageStatsInternal.addAppIdleStateChangeListener(this); + registerReceivers(); + } + + private void registerReceivers() { + // Monitor battery charging state + IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(mReceiver, filter); + } + + private boolean isPowered() { + // TODO: Allow any charger type + return mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_AC); + } + + @Override + public void maybeStartTrackingJob(JobStatus jobStatus) { + synchronized (mTrackedTasks) { + mTrackedTasks.add(jobStatus); + String packageName = jobStatus.job.getService().getPackageName(); + final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, + jobStatus.getUserId()); + if (DEBUG) { + Slog.d(LOG_TAG, "Start tracking, setting idle state of " + + packageName + " to " + appIdle); + } + jobStatus.appNotIdleConstraintSatisfied.set(!appIdle); + } + } + + @Override + public void maybeStopTrackingJob(JobStatus jobStatus) { + synchronized (mTrackedTasks) { + mTrackedTasks.remove(jobStatus); + } + } + + @Override + public void dumpControllerState(PrintWriter pw) { + // TODO: + } + + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { + boolean changed = false; + synchronized (mTrackedTasks) { + // If currently plugged in, we don't care about app idle state + if (mPluggedIn) { + return; + } + for (JobStatus task : mTrackedTasks) { + if (task.job.getService().getPackageName().equals(packageName) + && task.getUserId() == userId) { + if (task.appNotIdleConstraintSatisfied.get() != !idle) { + if (DEBUG) { + Slog.d(LOG_TAG, "App Idle state changed, setting idle state of " + + packageName + " to " + idle); + } + task.appNotIdleConstraintSatisfied.set(!idle); + changed = true; + } + } + } + } + if (changed) { + mStateChangedListener.onControllerStateChanged(); + } + } + + void onPluggedIn(boolean pluggedIn) { + // Flag if any app's idle state has changed + boolean changed = false; + synchronized (mTrackedTasks) { + if (mPluggedIn == pluggedIn) { + return; + } + mPluggedIn = pluggedIn; + for (JobStatus task : mTrackedTasks) { + String packageName = task.job.getService().getPackageName(); + final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, + task.getUserId()); + if (DEBUG) { + Slog.d(LOG_TAG, "Plugged in " + pluggedIn + ", setting idle state of " + + packageName + " to " + appIdle); + } + if (task.appNotIdleConstraintSatisfied.get() == appIdle) { + task.appNotIdleConstraintSatisfied.set(!appIdle); + changed = true; + } + } + } + if (changed) { + mStateChangedListener.onControllerStateChanged(); + } + } +} diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index e3c55b6..69c63f3 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -54,6 +54,7 @@ public class JobStatus { final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean unmeteredConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean(); + final AtomicBoolean appNotIdleConstraintSatisfied = new AtomicBoolean(); /** * Earliest point in the future at which this job will be eligible to run. A value of 0 @@ -199,8 +200,11 @@ public class JobStatus { * the constraints are satisfied <strong>or</strong> the deadline on the job has expired. */ public synchronized boolean isReady() { - return isConstraintsSatisfied() - || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get()); + // Deadline constraint trumps other constraints + // AppNotIdle implicit constraint trumps all! + return (isConstraintsSatisfied() + || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get())) + && appNotIdleConstraintSatisfied.get(); } /** @@ -229,6 +233,7 @@ public class JobStatus { + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging() + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures + ",P=" + job.isPersisted() + + ",ANI=" + appNotIdleConstraintSatisfied.get() + (isReady() ? "(READY)" : "") + "]"; } diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java index efd1928..cda7c32 100644 --- a/services/core/java/com/android/server/job/controllers/StateController.java +++ b/services/core/java/com/android/server/job/controllers/StateController.java @@ -44,7 +44,7 @@ public abstract class StateController { /** * Implement the logic here to decide whether a job should be tracked by this controller. - * This logic is put here so the JobManger can be completely agnostic of Controller logic. + * This logic is put here so the JobManager can be completely agnostic of Controller logic. * Also called when updating a task, so implementing controllers have to be aware of * preexisting tasks. */ |