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