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