Home | History | Annotate | Download | only in controllers
      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 
     17 package com.android.server.job.controllers;
     18 
     19 import android.app.usage.UsageStatsManagerInternal;
     20 import android.content.Context;
     21 import android.util.Slog;
     22 
     23 import com.android.server.LocalServices;
     24 import com.android.server.job.JobSchedulerService;
     25 import com.android.server.job.StateChangedListener;
     26 
     27 import java.io.PrintWriter;
     28 import java.util.ArrayList;
     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  */
     36 public 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     final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
     45     private final UsageStatsManagerInternal mUsageStatsInternal;
     46     boolean mAppIdleParoleOn;
     47 
     48     public static AppIdleController get(JobSchedulerService service) {
     49         synchronized (sCreationLock) {
     50             if (sController == null) {
     51                 sController = new AppIdleController(service, service.getContext());
     52             }
     53             return sController;
     54         }
     55     }
     56 
     57     private AppIdleController(StateChangedListener stateChangedListener, Context context) {
     58         super(stateChangedListener, context);
     59         mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
     60         mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
     61         mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
     62     }
     63 
     64     @Override
     65     public void maybeStartTrackingJob(JobStatus jobStatus) {
     66         synchronized (mTrackedTasks) {
     67             mTrackedTasks.add(jobStatus);
     68             String packageName = jobStatus.job.getService().getPackageName();
     69             final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
     70                     jobStatus.getUserId());
     71             if (DEBUG) {
     72                 Slog.d(LOG_TAG, "Start tracking, setting idle state of "
     73                         + packageName + " to " + appIdle);
     74             }
     75             jobStatus.appNotIdleConstraintSatisfied.set(!appIdle);
     76         }
     77     }
     78 
     79     @Override
     80     public void maybeStopTrackingJob(JobStatus jobStatus) {
     81         synchronized (mTrackedTasks) {
     82             mTrackedTasks.remove(jobStatus);
     83         }
     84     }
     85 
     86     @Override
     87     public void dumpControllerState(PrintWriter pw) {
     88         pw.println("AppIdle");
     89         pw.println("Parole On: " + mAppIdleParoleOn);
     90         synchronized (mTrackedTasks) {
     91             for (JobStatus task : mTrackedTasks) {
     92                 pw.print(task.job.getService().getPackageName());
     93                 pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get());
     94                 pw.print(", ");
     95             }
     96             pw.println();
     97         }
     98     }
     99 
    100     void setAppIdleParoleOn(boolean isAppIdleParoleOn) {
    101         // Flag if any app's idle state has changed
    102         boolean changed = false;
    103         synchronized (mTrackedTasks) {
    104             if (mAppIdleParoleOn == isAppIdleParoleOn) {
    105                 return;
    106             }
    107             mAppIdleParoleOn = isAppIdleParoleOn;
    108             for (JobStatus task : mTrackedTasks) {
    109                 String packageName = task.job.getService().getPackageName();
    110                 final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
    111                         task.getUserId());
    112                 if (DEBUG) {
    113                     Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
    114                 }
    115                 if (task.appNotIdleConstraintSatisfied.get() == appIdle) {
    116                     task.appNotIdleConstraintSatisfied.set(!appIdle);
    117                     changed = true;
    118                 }
    119             }
    120         }
    121         if (changed) {
    122             mStateChangedListener.onControllerStateChanged();
    123         }
    124     }
    125 
    126     private class AppIdleStateChangeListener
    127             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
    128         @Override
    129         public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
    130             boolean changed = false;
    131             synchronized (mTrackedTasks) {
    132                 if (mAppIdleParoleOn) {
    133                     return;
    134                 }
    135                 for (JobStatus task : mTrackedTasks) {
    136                     if (task.job.getService().getPackageName().equals(packageName)
    137                             && task.getUserId() == userId) {
    138                         if (task.appNotIdleConstraintSatisfied.get() != !idle) {
    139                             if (DEBUG) {
    140                                 Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
    141                                         + packageName + " to " + idle);
    142                             }
    143                             task.appNotIdleConstraintSatisfied.set(!idle);
    144                             changed = true;
    145                         }
    146                     }
    147                 }
    148             }
    149             if (changed) {
    150                 mStateChangedListener.onControllerStateChanged();
    151             }
    152         }
    153 
    154         @Override
    155         public void onParoleStateChanged(boolean isParoleOn) {
    156             if (DEBUG) {
    157                 Slog.d(LOG_TAG, "Parole on: " + isParoleOn);
    158             }
    159             setAppIdleParoleOn(isParoleOn);
    160         }
    161     }
    162 }
    163