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