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