blob: 0519b635a167f43af0165f593ab21b09ca03deae [file] [log] [blame]
Dianne Hackborn532ea262017-03-17 17:50:55 -07001/*
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
17package com.android.server.job.controllers;
18
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070019import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
20
Dianne Hackborn532ea262017-03-17 17:50:55 -070021import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
Dianne Hackborn532ea262017-03-17 17:50:55 -070025import android.os.UserHandle;
Dianne Hackbornf9bac162017-04-20 17:17:48 -070026import android.util.ArraySet;
Dianne Hackborn532ea262017-03-17 17:50:55 -070027import android.util.Slog;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080028import android.util.proto.ProtoOutputStream;
Dianne Hackborn532ea262017-03-17 17:50:55 -070029
30import com.android.internal.annotations.VisibleForTesting;
31import com.android.server.job.JobSchedulerService;
32import com.android.server.job.StateChangedListener;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080033import com.android.server.job.StateControllerProto;
Dianne Hackborn532ea262017-03-17 17:50:55 -070034import com.android.server.storage.DeviceStorageMonitorService;
35
36import java.io.PrintWriter;
Dianne Hackborn532ea262017-03-17 17:50:55 -070037
38/**
39 * Simple controller that tracks the status of the device's storage.
40 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -070041public final class StorageController extends StateController {
Dianne Hackborn532ea262017-03-17 17:50:55 -070042 private static final String TAG = "JobScheduler.Stor";
43
44 private static final Object sCreationLock = new Object();
45 private static volatile StorageController sController;
46
Dianne Hackbornf9bac162017-04-20 17:17:48 -070047 private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<JobStatus>();
Dianne Hackborn532ea262017-03-17 17:50:55 -070048 private StorageTracker mStorageTracker;
49
50 public static StorageController get(JobSchedulerService taskManagerService) {
51 synchronized (sCreationLock) {
52 if (sController == null) {
53 sController = new StorageController(taskManagerService,
54 taskManagerService.getContext(), taskManagerService.getLock());
55 }
56 }
57 return sController;
58 }
59
60 @VisibleForTesting
61 public StorageTracker getTracker() {
62 return mStorageTracker;
63 }
64
65 @VisibleForTesting
66 public static StorageController getForTesting(StateChangedListener stateChangedListener,
67 Context context) {
68 return new StorageController(stateChangedListener, context, new Object());
69 }
70
71 private StorageController(StateChangedListener stateChangedListener, Context context,
72 Object lock) {
73 super(stateChangedListener, context, lock);
74 mStorageTracker = new StorageTracker();
75 mStorageTracker.startTracking();
76 }
77
78 @Override
79 public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
80 if (taskStatus.hasStorageNotLowConstraint()) {
81 mTrackedTasks.add(taskStatus);
Dianne Hackbornf9bac162017-04-20 17:17:48 -070082 taskStatus.setTrackingController(JobStatus.TRACKING_STORAGE);
Dianne Hackborn532ea262017-03-17 17:50:55 -070083 taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
84 }
85 }
86
87 @Override
Dianne Hackbornf9bac162017-04-20 17:17:48 -070088 public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
89 boolean forUpdate) {
90 if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) {
Dianne Hackborn532ea262017-03-17 17:50:55 -070091 mTrackedTasks.remove(taskStatus);
92 }
93 }
94
95 private void maybeReportNewStorageState() {
96 final boolean storageNotLow = mStorageTracker.isStorageNotLow();
97 boolean reportChange = false;
98 synchronized (mLock) {
99 for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700100 final JobStatus ts = mTrackedTasks.valueAt(i);
Dianne Hackborn532ea262017-03-17 17:50:55 -0700101 boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow);
102 if (previous != storageNotLow) {
103 reportChange = true;
104 }
105 }
106 }
107 // Let the scheduler know that state has changed. This may or may not result in an
108 // execution.
109 if (reportChange) {
110 mStateChangedListener.onControllerStateChanged();
111 }
112 // Also tell the scheduler that any ready jobs should be flushed.
113 if (storageNotLow) {
114 mStateChangedListener.onRunJobNow(null);
115 }
116 }
117
Dianne Hackborn6466c1c2017-06-13 10:33:19 -0700118 public final class StorageTracker extends BroadcastReceiver {
Dianne Hackborn532ea262017-03-17 17:50:55 -0700119 /**
120 * Track whether storage is low.
121 */
122 private boolean mStorageLow;
123 /** Sequence number of last broadcast. */
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800124 private int mLastStorageSeq = -1;
Dianne Hackborn532ea262017-03-17 17:50:55 -0700125
126 public StorageTracker() {
127 }
128
129 public void startTracking() {
130 IntentFilter filter = new IntentFilter();
131
132 // Storage status. Just need to register, since STORAGE_LOW is a sticky
133 // broadcast we will receive that if it is currently active.
134 filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
135 filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
136 mContext.registerReceiver(this, filter);
137 }
138
139 public boolean isStorageNotLow() {
140 return !mStorageLow;
141 }
142
143 public int getSeq() {
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800144 return mLastStorageSeq;
Dianne Hackborn532ea262017-03-17 17:50:55 -0700145 }
146
147 @Override
148 public void onReceive(Context context, Intent intent) {
149 onReceiveInternal(intent);
150 }
151
152 @VisibleForTesting
153 public void onReceiveInternal(Intent intent) {
154 final String action = intent.getAction();
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800155 mLastStorageSeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
156 mLastStorageSeq);
Dianne Hackborn532ea262017-03-17 17:50:55 -0700157 if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
158 if (DEBUG) {
159 Slog.d(TAG, "Available storage too low to do work. @ "
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700160 + sElapsedRealtimeClock.millis());
Dianne Hackborn532ea262017-03-17 17:50:55 -0700161 }
162 mStorageLow = true;
163 } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
164 if (DEBUG) {
165 Slog.d(TAG, "Available stoage high enough to do work. @ "
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700166 + sElapsedRealtimeClock.millis());
Dianne Hackborn532ea262017-03-17 17:50:55 -0700167 }
168 mStorageLow = false;
169 maybeReportNewStorageState();
170 }
171 }
172 }
173
174 @Override
175 public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
176 pw.print("Storage: not low = ");
177 pw.print(mStorageTracker.isStorageNotLow());
178 pw.print(", seq=");
179 pw.println(mStorageTracker.getSeq());
180 pw.print("Tracking ");
181 pw.print(mTrackedTasks.size());
182 pw.println(":");
183 for (int i = 0; i < mTrackedTasks.size(); i++) {
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700184 final JobStatus js = mTrackedTasks.valueAt(i);
Dianne Hackborn532ea262017-03-17 17:50:55 -0700185 if (!js.shouldDump(filterUid)) {
186 continue;
187 }
188 pw.print(" #");
189 js.printUniqueId(pw);
190 pw.print(" from ");
191 UserHandle.formatUid(pw, js.getSourceUid());
192 pw.println();
193 }
194 }
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800195
196 @Override
197 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
198 final long token = proto.start(fieldId);
199 final long mToken = proto.start(StateControllerProto.STORAGE);
200
201 proto.write(StateControllerProto.StorageController.IS_STORAGE_NOT_LOW,
202 mStorageTracker.isStorageNotLow());
203 proto.write(StateControllerProto.StorageController.LAST_BROADCAST_SEQUENCE_NUMBER,
204 mStorageTracker.getSeq());
205
206 for (int i = 0; i < mTrackedTasks.size(); i++) {
207 final JobStatus js = mTrackedTasks.valueAt(i);
208 if (!js.shouldDump(filterUid)) {
209 continue;
210 }
211 final long jsToken = proto.start(StateControllerProto.StorageController.TRACKED_JOBS);
212 js.writeToShortProto(proto, StateControllerProto.StorageController.TrackedJob.INFO);
213 proto.write(StateControllerProto.StorageController.TrackedJob.SOURCE_UID,
214 js.getSourceUid());
215 proto.end(jsToken);
216 }
217
218 proto.end(mToken);
219 proto.end(token);
220 }
Dianne Hackborn532ea262017-03-17 17:50:55 -0700221}