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 android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.SystemClock; 24 import android.os.UserHandle; 25 import android.util.ArraySet; 26 import android.util.Slog; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.server.job.JobSchedulerService; 30 import com.android.server.job.StateChangedListener; 31 import com.android.server.storage.DeviceStorageMonitorService; 32 33 import java.io.PrintWriter; 34 35 /** 36 * Simple controller that tracks the status of the device's storage. 37 */ 38 public final class StorageController extends StateController { 39 private static final String TAG = "JobScheduler.Stor"; 40 41 private static final Object sCreationLock = new Object(); 42 private static volatile StorageController sController; 43 44 private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<JobStatus>(); 45 private StorageTracker mStorageTracker; 46 get(JobSchedulerService taskManagerService)47 public static StorageController get(JobSchedulerService taskManagerService) { 48 synchronized (sCreationLock) { 49 if (sController == null) { 50 sController = new StorageController(taskManagerService, 51 taskManagerService.getContext(), taskManagerService.getLock()); 52 } 53 } 54 return sController; 55 } 56 57 @VisibleForTesting getTracker()58 public StorageTracker getTracker() { 59 return mStorageTracker; 60 } 61 62 @VisibleForTesting getForTesting(StateChangedListener stateChangedListener, Context context)63 public static StorageController getForTesting(StateChangedListener stateChangedListener, 64 Context context) { 65 return new StorageController(stateChangedListener, context, new Object()); 66 } 67 StorageController(StateChangedListener stateChangedListener, Context context, Object lock)68 private StorageController(StateChangedListener stateChangedListener, Context context, 69 Object lock) { 70 super(stateChangedListener, context, lock); 71 mStorageTracker = new StorageTracker(); 72 mStorageTracker.startTracking(); 73 } 74 75 @Override maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob)76 public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { 77 if (taskStatus.hasStorageNotLowConstraint()) { 78 mTrackedTasks.add(taskStatus); 79 taskStatus.setTrackingController(JobStatus.TRACKING_STORAGE); 80 taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow()); 81 } 82 } 83 84 @Override maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate)85 public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, 86 boolean forUpdate) { 87 if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) { 88 mTrackedTasks.remove(taskStatus); 89 } 90 } 91 maybeReportNewStorageState()92 private void maybeReportNewStorageState() { 93 final boolean storageNotLow = mStorageTracker.isStorageNotLow(); 94 boolean reportChange = false; 95 synchronized (mLock) { 96 for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { 97 final JobStatus ts = mTrackedTasks.valueAt(i); 98 boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow); 99 if (previous != storageNotLow) { 100 reportChange = true; 101 } 102 } 103 } 104 // Let the scheduler know that state has changed. This may or may not result in an 105 // execution. 106 if (reportChange) { 107 mStateChangedListener.onControllerStateChanged(); 108 } 109 // Also tell the scheduler that any ready jobs should be flushed. 110 if (storageNotLow) { 111 mStateChangedListener.onRunJobNow(null); 112 } 113 } 114 115 public final class StorageTracker extends BroadcastReceiver { 116 /** 117 * Track whether storage is low. 118 */ 119 private boolean mStorageLow; 120 /** Sequence number of last broadcast. */ 121 private int mLastBatterySeq = -1; 122 StorageTracker()123 public StorageTracker() { 124 } 125 startTracking()126 public void startTracking() { 127 IntentFilter filter = new IntentFilter(); 128 129 // Storage status. Just need to register, since STORAGE_LOW is a sticky 130 // broadcast we will receive that if it is currently active. 131 filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); 132 filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 133 mContext.registerReceiver(this, filter); 134 } 135 isStorageNotLow()136 public boolean isStorageNotLow() { 137 return !mStorageLow; 138 } 139 getSeq()140 public int getSeq() { 141 return mLastBatterySeq; 142 } 143 144 @Override onReceive(Context context, Intent intent)145 public void onReceive(Context context, Intent intent) { 146 onReceiveInternal(intent); 147 } 148 149 @VisibleForTesting onReceiveInternal(Intent intent)150 public void onReceiveInternal(Intent intent) { 151 final String action = intent.getAction(); 152 mLastBatterySeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE, 153 mLastBatterySeq); 154 if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) { 155 if (DEBUG) { 156 Slog.d(TAG, "Available storage too low to do work. @ " 157 + SystemClock.elapsedRealtime()); 158 } 159 mStorageLow = true; 160 } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 161 if (DEBUG) { 162 Slog.d(TAG, "Available stoage high enough to do work. @ " 163 + SystemClock.elapsedRealtime()); 164 } 165 mStorageLow = false; 166 maybeReportNewStorageState(); 167 } 168 } 169 } 170 171 @Override dumpControllerStateLocked(PrintWriter pw, int filterUid)172 public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { 173 pw.print("Storage: not low = "); 174 pw.print(mStorageTracker.isStorageNotLow()); 175 pw.print(", seq="); 176 pw.println(mStorageTracker.getSeq()); 177 pw.print("Tracking "); 178 pw.print(mTrackedTasks.size()); 179 pw.println(":"); 180 for (int i = 0; i < mTrackedTasks.size(); i++) { 181 final JobStatus js = mTrackedTasks.valueAt(i); 182 if (!js.shouldDump(filterUid)) { 183 continue; 184 } 185 pw.print(" #"); 186 js.printUniqueId(pw); 187 pw.print(" from "); 188 UserHandle.formatUid(pw, js.getSourceUid()); 189 pw.println(); 190 } 191 } 192 } 193