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