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