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