Home | History | Annotate | Download | only in controllers
      1 /*
      2  * Copyright (C) 2017 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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
     20 
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.os.UserHandle;
     26 import android.util.ArraySet;
     27 import android.util.Log;
     28 import android.util.Slog;
     29 import android.util.proto.ProtoOutputStream;
     30 
     31 import com.android.internal.annotations.VisibleForTesting;
     32 import com.android.internal.util.IndentingPrintWriter;
     33 import com.android.server.job.JobSchedulerService;
     34 import com.android.server.job.StateControllerProto;
     35 import com.android.server.storage.DeviceStorageMonitorService;
     36 
     37 import java.util.function.Predicate;
     38 
     39 /**
     40  * Simple controller that tracks the status of the device's storage.
     41  */
     42 public final class StorageController extends StateController {
     43     private static final String TAG = "JobScheduler.Storage";
     44     private static final boolean DEBUG = JobSchedulerService.DEBUG
     45             || Log.isLoggable(TAG, Log.DEBUG);
     46 
     47     private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<JobStatus>();
     48     private final StorageTracker mStorageTracker;
     49 
     50     @VisibleForTesting
     51     public StorageTracker getTracker() {
     52         return mStorageTracker;
     53     }
     54 
     55     public StorageController(JobSchedulerService service) {
     56         super(service);
     57         mStorageTracker = new StorageTracker();
     58         mStorageTracker.startTracking();
     59     }
     60 
     61     @Override
     62     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
     63         if (taskStatus.hasStorageNotLowConstraint()) {
     64             mTrackedTasks.add(taskStatus);
     65             taskStatus.setTrackingController(JobStatus.TRACKING_STORAGE);
     66             taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
     67         }
     68     }
     69 
     70     @Override
     71     public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
     72             boolean forUpdate) {
     73         if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) {
     74             mTrackedTasks.remove(taskStatus);
     75         }
     76     }
     77 
     78     private void maybeReportNewStorageState() {
     79         final boolean storageNotLow = mStorageTracker.isStorageNotLow();
     80         boolean reportChange = false;
     81         synchronized (mLock) {
     82             for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
     83                 final JobStatus ts = mTrackedTasks.valueAt(i);
     84                 boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow);
     85                 if (previous != storageNotLow) {
     86                     reportChange = true;
     87                 }
     88             }
     89         }
     90         // Let the scheduler know that state has changed. This may or may not result in an
     91         // execution.
     92         if (reportChange) {
     93             mStateChangedListener.onControllerStateChanged();
     94         }
     95         // Also tell the scheduler that any ready jobs should be flushed.
     96         if (storageNotLow) {
     97             mStateChangedListener.onRunJobNow(null);
     98         }
     99     }
    100 
    101     public final class StorageTracker extends BroadcastReceiver {
    102         /**
    103          * Track whether storage is low.
    104          */
    105         private boolean mStorageLow;
    106         /** Sequence number of last broadcast. */
    107         private int mLastStorageSeq = -1;
    108 
    109         public StorageTracker() {
    110         }
    111 
    112         public void startTracking() {
    113             IntentFilter filter = new IntentFilter();
    114 
    115             // Storage status.  Just need to register, since STORAGE_LOW is a sticky
    116             // broadcast we will receive that if it is currently active.
    117             filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
    118             filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
    119             mContext.registerReceiver(this, filter);
    120         }
    121 
    122         public boolean isStorageNotLow() {
    123             return !mStorageLow;
    124         }
    125 
    126         public int getSeq() {
    127             return mLastStorageSeq;
    128         }
    129 
    130         @Override
    131         public void onReceive(Context context, Intent intent) {
    132             onReceiveInternal(intent);
    133         }
    134 
    135         @VisibleForTesting
    136         public void onReceiveInternal(Intent intent) {
    137             final String action = intent.getAction();
    138             mLastStorageSeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
    139                     mLastStorageSeq);
    140             if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
    141                 if (DEBUG) {
    142                     Slog.d(TAG, "Available storage too low to do work. @ "
    143                             + sElapsedRealtimeClock.millis());
    144                 }
    145                 mStorageLow = true;
    146             } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
    147                 if (DEBUG) {
    148                     Slog.d(TAG, "Available stoage high enough to do work. @ "
    149                             + sElapsedRealtimeClock.millis());
    150                 }
    151                 mStorageLow = false;
    152                 maybeReportNewStorageState();
    153             }
    154         }
    155     }
    156 
    157     @Override
    158     public void dumpControllerStateLocked(IndentingPrintWriter pw,
    159             Predicate<JobStatus> predicate) {
    160         pw.println("Not low: " + mStorageTracker.isStorageNotLow());
    161         pw.println("Sequence: " + mStorageTracker.getSeq());
    162         pw.println();
    163 
    164         for (int i = 0; i < mTrackedTasks.size(); i++) {
    165             final JobStatus js = mTrackedTasks.valueAt(i);
    166             if (!predicate.test(js)) {
    167                 continue;
    168             }
    169             pw.print("#");
    170             js.printUniqueId(pw);
    171             pw.print(" from ");
    172             UserHandle.formatUid(pw, js.getSourceUid());
    173             pw.println();
    174         }
    175     }
    176 
    177     @Override
    178     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
    179             Predicate<JobStatus> predicate) {
    180         final long token = proto.start(fieldId);
    181         final long mToken = proto.start(StateControllerProto.STORAGE);
    182 
    183         proto.write(StateControllerProto.StorageController.IS_STORAGE_NOT_LOW,
    184                 mStorageTracker.isStorageNotLow());
    185         proto.write(StateControllerProto.StorageController.LAST_BROADCAST_SEQUENCE_NUMBER,
    186                 mStorageTracker.getSeq());
    187 
    188         for (int i = 0; i < mTrackedTasks.size(); i++) {
    189             final JobStatus js = mTrackedTasks.valueAt(i);
    190             if (!predicate.test(js)) {
    191                 continue;
    192             }
    193             final long jsToken = proto.start(StateControllerProto.StorageController.TRACKED_JOBS);
    194             js.writeToShortProto(proto, StateControllerProto.StorageController.TrackedJob.INFO);
    195             proto.write(StateControllerProto.StorageController.TrackedJob.SOURCE_UID,
    196                     js.getSourceUid());
    197             proto.end(jsToken);
    198         }
    199 
    200         proto.end(mToken);
    201         proto.end(token);
    202     }
    203 }
    204