Home | History | Annotate | Download | only in controllers
      1 /*
      2  * Copyright (C) 2016 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.job.JobInfo;
     20 import android.content.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.PowerManager;
     28 import android.os.UserHandle;
     29 import android.util.ArraySet;
     30 import android.util.Log;
     31 import android.util.Slog;
     32 import android.util.SparseBooleanArray;
     33 import android.util.proto.ProtoOutputStream;
     34 
     35 import com.android.internal.util.ArrayUtils;
     36 import com.android.internal.util.IndentingPrintWriter;
     37 import com.android.server.DeviceIdleController;
     38 import com.android.server.LocalServices;
     39 import com.android.server.job.JobSchedulerService;
     40 import com.android.server.job.StateControllerProto;
     41 import com.android.server.job.StateControllerProto.DeviceIdleJobsController.TrackedJob;
     42 
     43 import java.util.Arrays;
     44 import java.util.function.Consumer;
     45 import java.util.function.Predicate;
     46 
     47 /**
     48  * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
     49  * When device is not dozing, set constraint for all jobs as satisfied.
     50  */
     51 public final class DeviceIdleJobsController extends StateController {
     52     private static final String TAG = "JobScheduler.DeviceIdle";
     53     private static final boolean DEBUG = JobSchedulerService.DEBUG
     54             || Log.isLoggable(TAG, Log.DEBUG);
     55 
     56     private static final long BACKGROUND_JOBS_DELAY = 3000;
     57 
     58     static final int PROCESS_BACKGROUND_JOBS = 1;
     59 
     60     /**
     61      * These are jobs added with a special flag to indicate that they should be exempted from doze
     62      * when the app is temp whitelisted or in the foreground.
     63      */
     64     private final ArraySet<JobStatus> mAllowInIdleJobs;
     65     private final SparseBooleanArray mForegroundUids;
     66     private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
     67     private final DeviceIdleJobsDelayHandler mHandler;
     68     private final PowerManager mPowerManager;
     69     private final DeviceIdleController.LocalService mLocalDeviceIdleController;
     70 
     71     /**
     72      * True when in device idle mode, so we don't want to schedule any jobs.
     73      */
     74     private boolean mDeviceIdleMode;
     75     private int[] mDeviceIdleWhitelistAppIds;
     76     private int[] mPowerSaveTempWhitelistAppIds;
     77 
     78     // onReceive
     79     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
     80         @Override
     81         public void onReceive(Context context, Intent intent) {
     82             switch (intent.getAction()) {
     83                 case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
     84                 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
     85                     updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
     86                             || mPowerManager.isLightDeviceIdleMode()));
     87                     break;
     88                 case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
     89                     synchronized (mLock) {
     90                         mDeviceIdleWhitelistAppIds =
     91                                 mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
     92                         if (DEBUG) {
     93                             Slog.d(TAG, "Got whitelist "
     94                                     + Arrays.toString(mDeviceIdleWhitelistAppIds));
     95                         }
     96                     }
     97                     break;
     98                 case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
     99                     synchronized (mLock) {
    100                         mPowerSaveTempWhitelistAppIds =
    101                                 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
    102                         if (DEBUG) {
    103                             Slog.d(TAG, "Got temp whitelist "
    104                                     + Arrays.toString(mPowerSaveTempWhitelistAppIds));
    105                         }
    106                         boolean changed = false;
    107                         for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
    108                             changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
    109                         }
    110                         if (changed) {
    111                             mStateChangedListener.onControllerStateChanged();
    112                         }
    113                     }
    114                     break;
    115             }
    116         }
    117     };
    118 
    119     public DeviceIdleJobsController(JobSchedulerService service) {
    120         super(service);
    121 
    122         mHandler = new DeviceIdleJobsDelayHandler(mContext.getMainLooper());
    123         // Register for device idle mode changes
    124         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    125         mLocalDeviceIdleController =
    126                 LocalServices.getService(DeviceIdleController.LocalService.class);
    127         mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
    128         mPowerSaveTempWhitelistAppIds =
    129                 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
    130         mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
    131         mAllowInIdleJobs = new ArraySet<>();
    132         mForegroundUids = new SparseBooleanArray();
    133         final IntentFilter filter = new IntentFilter();
    134         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
    135         filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
    136         filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
    137         filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
    138         mContext.registerReceiverAsUser(
    139                 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
    140     }
    141 
    142     void updateIdleMode(boolean enabled) {
    143         boolean changed = false;
    144         synchronized (mLock) {
    145             if (mDeviceIdleMode != enabled) {
    146                 changed = true;
    147             }
    148             mDeviceIdleMode = enabled;
    149             if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
    150             if (enabled) {
    151                 mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
    152                 mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
    153             } else {
    154                 // When coming out of doze, process all foreground uids immediately, while others
    155                 // will be processed after a delay of 3 seconds.
    156                 for (int i = 0; i < mForegroundUids.size(); i++) {
    157                     if (mForegroundUids.valueAt(i)) {
    158                         mService.getJobStore().forEachJobForSourceUid(
    159                                 mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
    160                     }
    161                 }
    162                 mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
    163             }
    164         }
    165         // Inform the job scheduler service about idle mode changes
    166         if (changed) {
    167             mStateChangedListener.onDeviceIdleStateChanged(enabled);
    168         }
    169     }
    170 
    171     /**
    172      *  Called by jobscheduler service to report uid state changes between active and idle
    173      */
    174     public void setUidActiveLocked(int uid, boolean active) {
    175         final boolean changed = (active != mForegroundUids.get(uid));
    176         if (!changed) {
    177             return;
    178         }
    179         if (DEBUG) {
    180             Slog.d(TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
    181         }
    182         mForegroundUids.put(uid, active);
    183         mDeviceIdleUpdateFunctor.mChanged = false;
    184         mService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
    185         if (mDeviceIdleUpdateFunctor.mChanged) {
    186             mStateChangedListener.onControllerStateChanged();
    187         }
    188     }
    189 
    190     /**
    191      * Checks if the given job's scheduling app id exists in the device idle user whitelist.
    192      */
    193     boolean isWhitelistedLocked(JobStatus job) {
    194         return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
    195                 UserHandle.getAppId(job.getSourceUid())) >= 0;
    196     }
    197 
    198     /**
    199      * Checks if the given job's scheduling app id exists in the device idle temp whitelist.
    200      */
    201     boolean isTempWhitelistedLocked(JobStatus job) {
    202         return ArrayUtils.contains(mPowerSaveTempWhitelistAppIds,
    203                 UserHandle.getAppId(job.getSourceUid()));
    204     }
    205 
    206     private boolean updateTaskStateLocked(JobStatus task) {
    207         final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
    208                 && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
    209         final boolean whitelisted = isWhitelistedLocked(task);
    210         final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
    211         return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
    212     }
    213 
    214     @Override
    215     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
    216         if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
    217             mAllowInIdleJobs.add(jobStatus);
    218         }
    219         updateTaskStateLocked(jobStatus);
    220     }
    221 
    222     @Override
    223     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
    224             boolean forUpdate) {
    225         if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
    226             mAllowInIdleJobs.remove(jobStatus);
    227         }
    228     }
    229 
    230     @Override
    231     public void dumpControllerStateLocked(final IndentingPrintWriter pw,
    232             final Predicate<JobStatus> predicate) {
    233         pw.println("Idle mode: " + mDeviceIdleMode);
    234         pw.println();
    235 
    236         mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
    237             pw.print("#");
    238             jobStatus.printUniqueId(pw);
    239             pw.print(" from ");
    240             UserHandle.formatUid(pw, jobStatus.getSourceUid());
    241             pw.print(": ");
    242             pw.print(jobStatus.getSourcePackageName());
    243             pw.print((jobStatus.satisfiedConstraints
    244                     & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
    245                             ? " RUNNABLE" : " WAITING");
    246             if (jobStatus.dozeWhitelisted) {
    247                 pw.print(" WHITELISTED");
    248             }
    249             if (mAllowInIdleJobs.contains(jobStatus)) {
    250                 pw.print(" ALLOWED_IN_DOZE");
    251             }
    252             pw.println();
    253         });
    254     }
    255 
    256     @Override
    257     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
    258             Predicate<JobStatus> predicate) {
    259         final long token = proto.start(fieldId);
    260         final long mToken = proto.start(StateControllerProto.DEVICE_IDLE);
    261 
    262         proto.write(StateControllerProto.DeviceIdleJobsController.IS_DEVICE_IDLE_MODE,
    263                 mDeviceIdleMode);
    264         mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
    265             final long jsToken =
    266                     proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS);
    267 
    268             jobStatus.writeToShortProto(proto, TrackedJob.INFO);
    269             proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid());
    270             proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
    271             proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
    272                     (jobStatus.satisfiedConstraints &
    273                         JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
    274             proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
    275             proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
    276 
    277             proto.end(jsToken);
    278         });
    279 
    280         proto.end(mToken);
    281         proto.end(token);
    282     }
    283 
    284     final class DeviceIdleUpdateFunctor implements Consumer<JobStatus> {
    285         boolean mChanged;
    286 
    287         @Override
    288         public void accept(JobStatus jobStatus) {
    289             mChanged |= updateTaskStateLocked(jobStatus);
    290         }
    291     }
    292 
    293     final class DeviceIdleJobsDelayHandler extends Handler {
    294         public DeviceIdleJobsDelayHandler(Looper looper) {
    295             super(looper);
    296         }
    297 
    298         @Override
    299         public void handleMessage(Message msg) {
    300             switch (msg.what) {
    301                 case PROCESS_BACKGROUND_JOBS:
    302                     // Just process all the jobs, the ones in foreground should already be running.
    303                     synchronized (mLock) {
    304                         mDeviceIdleUpdateFunctor.mChanged = false;
    305                         mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
    306                         if (mDeviceIdleUpdateFunctor.mChanged) {
    307                             mStateChangedListener.onControllerStateChanged();
    308                         }
    309                     }
    310                     break;
    311             }
    312         }
    313     }
    314 }
    315