AppIdleController.java revision 8db0fc15b85c6501a0418b17edee2d9c447b408a
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.util.Slog;
22
23import com.android.server.LocalServices;
24import com.android.server.job.JobSchedulerService;
25import com.android.server.job.JobStore;
26import com.android.server.job.StateChangedListener;
27
28import java.io.PrintWriter;
29import java.util.ArrayList;
30
31/**
32 * Controls when apps are considered idle and if jobs pertaining to those apps should
33 * be executed. Apps that haven't been actively launched or accessed from a foreground app
34 * for a certain amount of time (maybe hours or days) are considered idle. When the app comes
35 * out of idle state, it will be allowed to run scheduled jobs.
36 */
37public class AppIdleController extends StateController {
38
39    private static final String LOG_TAG = "AppIdleController";
40    private static final boolean DEBUG = false;
41
42    // Singleton factory
43    private static Object sCreationLock = new Object();
44    private static volatile AppIdleController sController;
45    private final JobSchedulerService mJobSchedulerService;
46    private final UsageStatsManagerInternal mUsageStatsInternal;
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 = mUsageStatsInternal.isAppIdleParoleOn();
106        mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
107    }
108
109    @Override
110    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
111        String packageName = jobStatus.getSourcePackageName();
112        final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
113                jobStatus.getSourceUid(), jobStatus.getSourceUserId());
114        if (DEBUG) {
115            Slog.d(LOG_TAG, "Start tracking, setting idle state of "
116                    + packageName + " to " + appIdle);
117        }
118        jobStatus.setAppNotIdleConstraintSatisfied(!appIdle);
119    }
120
121    @Override
122    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
123    }
124
125    @Override
126    public void dumpControllerStateLocked(final PrintWriter pw) {
127        pw.println("AppIdle");
128        pw.println("Parole On: " + mAppIdleParoleOn);
129        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
130            @Override public void process(JobStatus jobStatus) {
131                pw.print("  ");
132                pw.print(jobStatus.getSourcePackageName());
133                pw.print(": runnable=");
134                pw.println((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
135            }
136        });
137        pw.println();
138    }
139
140    void setAppIdleParoleOn(boolean isAppIdleParoleOn) {
141        // Flag if any app's idle state has changed
142        boolean changed = false;
143        synchronized (mLock) {
144            if (mAppIdleParoleOn == isAppIdleParoleOn) {
145                return;
146            }
147            mAppIdleParoleOn = isAppIdleParoleOn;
148            GlobalUpdateFunc update = new GlobalUpdateFunc();
149            mJobSchedulerService.getJobStore().forEachJob(update);
150            if (update.mChanged) {
151                changed = true;
152            }
153        }
154        if (changed) {
155            mStateChangedListener.onControllerStateChanged();
156        }
157    }
158
159    private class AppIdleStateChangeListener
160            extends UsageStatsManagerInternal.AppIdleStateChangeListener {
161        @Override
162        public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
163            boolean changed = false;
164            synchronized (mLock) {
165                if (mAppIdleParoleOn) {
166                    return;
167                }
168                PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
169                mJobSchedulerService.getJobStore().forEachJob(update);
170                if (update.mChanged) {
171                    changed = true;
172                }
173            }
174            if (changed) {
175                mStateChangedListener.onControllerStateChanged();
176            }
177        }
178
179        @Override
180        public void onParoleStateChanged(boolean isParoleOn) {
181            if (DEBUG) {
182                Slog.d(LOG_TAG, "Parole on: " + isParoleOn);
183            }
184            setAppIdleParoleOn(isParoleOn);
185        }
186    }
187}
188