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