blob: 83cc06d90fe63a3655ade6c4201a936b6ed7e9e1 [file] [log] [blame]
Enrico Granata517a1e02017-09-20 16:15:50 -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.car;
18
Pavel Maltseved2c8642017-12-18 12:56:26 -080019import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNotNull;
22import static org.junit.Assert.assertNull;
23import static org.junit.Assert.assertTrue;
24import static org.junit.Assert.fail;
25
Enrico Granata286cd8e2017-09-25 14:46:49 -070026import android.annotation.NonNull;
27import android.annotation.Nullable;
Enrico Granata517a1e02017-09-20 16:15:50 -070028import android.car.Car;
29import android.car.storagemonitoring.CarStorageMonitoringManager;
Enrico Granatadbda56f2017-12-18 11:53:22 -080030import android.car.storagemonitoring.IoStatsEntry;
31import android.car.storagemonitoring.IoStats;
Enrico Granata1690a622018-01-22 17:34:46 -080032import android.car.storagemonitoring.LifetimeWriteInfo;
Enrico Granata7e0150d2017-11-06 17:20:17 -080033import android.car.storagemonitoring.UidIoRecord;
Enrico Granata1172f882017-09-21 14:51:30 -070034import android.car.storagemonitoring.WearEstimate;
Enrico Granata286cd8e2017-09-25 14:46:49 -070035import android.car.storagemonitoring.WearEstimateChange;
Enrico Granata43024de2017-11-16 17:09:37 -080036import android.content.Intent;
Enrico Granata57738692018-02-23 15:35:07 -080037import android.content.pm.PackageManager.NameNotFoundException;
38import android.content.pm.ResolveInfo;
Enrico Granata7dce3792017-11-09 14:13:28 -080039import android.os.SystemClock;
Pavel Maltseved2c8642017-12-18 12:56:26 -080040import android.support.test.filters.MediumTest;
41import android.support.test.runner.AndroidJUnit4;
Enrico Granata286cd8e2017-09-25 14:46:49 -070042import android.util.JsonWriter;
43import android.util.Log;
Enrico Granatab19bc322017-10-12 12:25:06 -070044import android.util.Pair;
Enrico Granataa97fce22017-10-27 14:57:34 -070045import android.util.SparseArray;
Pavel Maltseved2c8642017-12-18 12:56:26 -080046
Enrico Granata1690a622018-01-22 17:34:46 -080047import com.android.car.storagemonitoring.LifetimeWriteInfoProvider;
Enrico Granataa97fce22017-10-27 14:57:34 -070048import com.android.car.storagemonitoring.UidIoStatsProvider;
Enrico Granata286cd8e2017-09-25 14:46:49 -070049import com.android.car.storagemonitoring.WearEstimateRecord;
50import com.android.car.storagemonitoring.WearHistory;
Enrico Granata517a1e02017-09-20 16:15:50 -070051import com.android.car.storagemonitoring.WearInformation;
Enrico Granatab19bc322017-10-12 12:25:06 -070052import com.android.car.storagemonitoring.WearInformationProvider;
53import com.android.car.systeminterface.StorageMonitoringInterface;
54import com.android.car.systeminterface.SystemInterface;
55import com.android.car.systeminterface.SystemStateInterface;
56import com.android.car.systeminterface.TimeInterface;
Pavel Maltseved2c8642017-12-18 12:56:26 -080057
Enrico Granata1690a622018-01-22 17:34:46 -080058import com.android.car.test.utils.TemporaryFile;
Enrico Granata57738692018-02-23 15:35:07 -080059import java.util.ArrayDeque;
Enrico Granata1690a622018-01-22 17:34:46 -080060import java.util.Collection;
Enrico Granata57738692018-02-23 15:35:07 -080061import java.util.Objects;
62import java.util.Queue;
Enrico Granata1690a622018-01-22 17:34:46 -080063import org.json.JSONArray;
64import org.json.JSONObject;
Pavel Maltseved2c8642017-12-18 12:56:26 -080065import org.junit.Rule;
66import org.junit.Test;
67import org.junit.rules.TestName;
68import org.junit.runner.RunWith;
69
Enrico Granata286cd8e2017-09-25 14:46:49 -070070import java.io.File;
71import java.io.FileWriter;
72import java.io.IOException;
Enrico Granatab19bc322017-10-12 12:25:06 -070073import java.time.Duration;
Enrico Granata286cd8e2017-09-25 14:46:49 -070074import java.time.Instant;
Enrico Granatab19bc322017-10-12 12:25:06 -070075import java.util.ArrayList;
Enrico Granataa97fce22017-10-27 14:57:34 -070076import java.util.Arrays;
Enrico Granatab19bc322017-10-12 12:25:06 -070077import java.util.Comparator;
Enrico Granata286cd8e2017-09-25 14:46:49 -070078import java.util.HashMap;
79import java.util.List;
80import java.util.Map;
Enrico Granata517a1e02017-09-20 16:15:50 -070081
82/** Test the public entry points for the CarStorageMonitoringManager */
Pavel Maltseved2c8642017-12-18 12:56:26 -080083@RunWith(AndroidJUnit4.class)
Enrico Granata517a1e02017-09-20 16:15:50 -070084@MediumTest
85public class CarStorageMonitoringTest extends MockedCarTestBase {
86 private static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
87
Pavel Maltseved2c8642017-12-18 12:56:26 -080088 @Rule public TestName mTestName = new TestName();
89
Enrico Granata1172f882017-09-21 14:51:30 -070090 private static final WearInformation DEFAULT_WEAR_INFORMATION =
Enrico Granata286cd8e2017-09-25 14:46:49 -070091 new WearInformation(30, 0, WearInformation.PRE_EOL_INFO_NORMAL);
92
Enrico Granata57738692018-02-23 15:35:07 -080093 final class TestContext extends MockContext {
94 TestContext(MockContext context) {
95 super(context.getBaseContext());
96 }
97
98 @Override
99 public void sendBroadcast(Intent intent, String receiverPermission) {
100 Log.d(TAG, "test context broadcasting " + intent);
101 if (CarStorageMonitoringManager.INTENT_EXCESSIVE_IO.equals(intent.getAction())) {
102 assertEquals(Car.PERMISSION_STORAGE_MONITORING, receiverPermission);
103
104 List<ResolveInfo> resolveInfoList = mContext.getPackageManager().
105 queryBroadcastReceivers(intent, 0);
106
107 assertEquals(1,
108 resolveInfoList.stream().map(ri -> ri.activityInfo)
109 .filter(Objects::nonNull)
110 .map(ai -> ai.name)
111 .filter(CarStorageMonitoringBroadcastReceiver.class
112 .getCanonicalName()::equals)
113 .count());
114
115 CarStorageMonitoringBroadcastReceiver.deliver(intent);
116 } else {
117 super.sendBroadcast(intent, receiverPermission);
118 }
119 }
120 }
121
Enrico Granata43024de2017-11-16 17:09:37 -0800122 static class ResourceOverrides {
123 private final HashMap<Integer, Integer> mIntegerOverrides = new HashMap<>();
124 private final HashMap<Integer, String> mStringOverrides = new HashMap<>();
125
126 void override(int id, int value) {
127 mIntegerOverrides.put(id, value);
128 }
129 void override(int id, String value) {
130 mStringOverrides.put(id, value);
131 }
132
133 void overrideResources(MockResources resources) {
134 mIntegerOverrides.forEach(resources::overrideResource);
135 mStringOverrides.forEach(resources::overrideResource);
136 }
137 }
138
Pavel Maltseved2c8642017-12-18 12:56:26 -0800139 private final Map<String, ResourceOverrides> PER_TEST_RESOURCES =
Enrico Granata43024de2017-11-16 17:09:37 -0800140 new HashMap<String, ResourceOverrides>() {
141 {
142 put("testIntentOnExcessiveWrite",
143 new ResourceOverrides() {{
144 override(R.integer.maxExcessiveIoSamplesInWindow, 0);
145 override(R.integer.acceptableWrittenKBytesPerSample, 10);
146 override(R.integer.acceptableFsyncCallsPerSample, 1000);
147 override(R.string.intentReceiverForUnacceptableIoMetrics,
Pavel Maltseved2c8642017-12-18 12:56:26 -0800148 getFlattenComponent(
149 CarStorageMonitoringBroadcastReceiver.class));
Enrico Granata43024de2017-11-16 17:09:37 -0800150 }});
151
152 put("testIntentOnExcessiveFsync",
Pavel Maltseved2c8642017-12-18 12:56:26 -0800153 new ResourceOverrides() {{
154 override(R.integer.maxExcessiveIoSamplesInWindow, 0);
155 override(R.integer.acceptableWrittenKBytesPerSample, 1000);
156 override(R.integer.acceptableFsyncCallsPerSample, 2);
157 override(R.string.intentReceiverForUnacceptableIoMetrics,
158 getFlattenComponent(
159 CarStorageMonitoringBroadcastReceiver.class));
160 }});
Enrico Granata88d92c82017-11-30 16:47:15 -0800161
162 put("testZeroWindowDisablesCollection",
Pavel Maltseved2c8642017-12-18 12:56:26 -0800163 new ResourceOverrides() {{
164 override(R.integer.ioStatsNumSamplesToStore, 0);
165 }});
Enrico Granata88d92c82017-11-30 16:47:15 -0800166
Enrico Granata43024de2017-11-16 17:09:37 -0800167 }
168 };
169
Enrico Granataa97fce22017-10-27 14:57:34 -0700170 private static final class TestData {
171 static final TestData DEFAULT = new TestData(0, DEFAULT_WEAR_INFORMATION, null, null);
Enrico Granata286cd8e2017-09-25 14:46:49 -0700172
173 final long uptime;
174 @NonNull
175 final WearInformation wearInformation;
176 @Nullable
177 final WearHistory wearHistory;
Enrico Granataa97fce22017-10-27 14:57:34 -0700178 @NonNull
Enrico Granata7e0150d2017-11-06 17:20:17 -0800179 final UidIoRecord[] ioStats;
Enrico Granata1690a622018-01-22 17:34:46 -0800180 @NonNull
181 final LifetimeWriteInfo[] previousLifetimeWriteInfo;
182 @NonNull
183 final LifetimeWriteInfo[] currentLifetimeWriteInfo;
Enrico Granata286cd8e2017-09-25 14:46:49 -0700184
Enrico Granataa97fce22017-10-27 14:57:34 -0700185 TestData(long uptime,
Enrico Granata1690a622018-01-22 17:34:46 -0800186 @Nullable WearInformation wearInformation,
187 @Nullable WearHistory wearHistory,
188 @Nullable UidIoRecord[] ioStats) {
189 this(uptime, wearInformation, wearHistory, ioStats, null, null);
190 }
191
192 TestData(long uptime,
Enrico Granataa97fce22017-10-27 14:57:34 -0700193 @Nullable WearInformation wearInformation,
194 @Nullable WearHistory wearHistory,
Enrico Granata1690a622018-01-22 17:34:46 -0800195 @Nullable UidIoRecord[] ioStats,
196 @Nullable LifetimeWriteInfo[] previousLifetimeWriteInfo,
197 @Nullable LifetimeWriteInfo[] currentLifetimeWriteInfo) {
Enrico Granata286cd8e2017-09-25 14:46:49 -0700198 if (wearInformation == null) wearInformation = DEFAULT_WEAR_INFORMATION;
Enrico Granata7e0150d2017-11-06 17:20:17 -0800199 if (ioStats == null) ioStats = new UidIoRecord[0];
Enrico Granata1690a622018-01-22 17:34:46 -0800200 if (previousLifetimeWriteInfo == null) {
201 previousLifetimeWriteInfo = new LifetimeWriteInfo[0];
202 }
203 if (currentLifetimeWriteInfo == null) {
204 currentLifetimeWriteInfo = new LifetimeWriteInfo[0];
205 }
Enrico Granata286cd8e2017-09-25 14:46:49 -0700206 this.uptime = uptime;
207 this.wearInformation = wearInformation;
208 this.wearHistory = wearHistory;
Enrico Granataa97fce22017-10-27 14:57:34 -0700209 this.ioStats = ioStats;
Enrico Granata1690a622018-01-22 17:34:46 -0800210 this.previousLifetimeWriteInfo = previousLifetimeWriteInfo;
211 this.currentLifetimeWriteInfo = currentLifetimeWriteInfo;
Enrico Granata286cd8e2017-09-25 14:46:49 -0700212 }
213 }
214
Enrico Granataa97fce22017-10-27 14:57:34 -0700215 private static final Map<String, TestData> PER_TEST_DATA =
Enrico Granata7e0150d2017-11-06 17:20:17 -0800216 new HashMap<String, TestData>() {
217 {
218 put("testReadWearHistory",
219 new TestData(6500, DEFAULT_WEAR_INFORMATION,
220 WearHistory.fromRecords(
221 WearEstimateRecord.Builder.newBuilder()
222 .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
223 .toWearEstimate(new WearEstimate(10, 0))
224 .atUptime(1000)
225 .atTimestamp(Instant.ofEpochMilli(5000)).build(),
226 WearEstimateRecord.Builder.newBuilder()
227 .fromWearEstimate(new WearEstimate(10, 0))
228 .toWearEstimate(new WearEstimate(20, 0))
229 .atUptime(4000)
230 .atTimestamp(Instant.ofEpochMilli(12000)).build(),
231 WearEstimateRecord.Builder.newBuilder()
232 .fromWearEstimate(new WearEstimate(20, 0))
233 .toWearEstimate(new WearEstimate(30, 0))
234 .atUptime(6500)
235 .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
Enrico Granata286cd8e2017-09-25 14:46:49 -0700236
Enrico Granata7e0150d2017-11-06 17:20:17 -0800237 put("testNotAcceptableWearEvent",
238 new TestData(2520006499L,
239 new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
240 WearHistory.fromRecords(
241 WearEstimateRecord.Builder.newBuilder()
242 .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
243 .toWearEstimate(new WearEstimate(10, 0))
244 .atUptime(1000)
245 .atTimestamp(Instant.ofEpochMilli(5000)).build(),
246 WearEstimateRecord.Builder.newBuilder()
247 .fromWearEstimate(new WearEstimate(10, 0))
248 .toWearEstimate(new WearEstimate(20, 0))
249 .atUptime(4000)
250 .atTimestamp(Instant.ofEpochMilli(12000)).build(),
251 WearEstimateRecord.Builder.newBuilder()
252 .fromWearEstimate(new WearEstimate(20, 0))
253 .toWearEstimate(new WearEstimate(30, 0))
254 .atUptime(6500)
255 .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
Enrico Granata286cd8e2017-09-25 14:46:49 -0700256
Enrico Granata7e0150d2017-11-06 17:20:17 -0800257 put("testAcceptableWearEvent",
258 new TestData(2520006501L,
259 new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
260 WearHistory.fromRecords(
261 WearEstimateRecord.Builder.newBuilder()
262 .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
263 .toWearEstimate(new WearEstimate(10, 0))
264 .atUptime(1000)
265 .atTimestamp(Instant.ofEpochMilli(5000)).build(),
266 WearEstimateRecord.Builder.newBuilder()
267 .fromWearEstimate(new WearEstimate(10, 0))
268 .toWearEstimate(new WearEstimate(20, 0))
269 .atUptime(4000)
270 .atTimestamp(Instant.ofEpochMilli(12000)).build(),
271 WearEstimateRecord.Builder.newBuilder()
272 .fromWearEstimate(new WearEstimate(20, 0))
273 .toWearEstimate(new WearEstimate(30, 0))
274 .atUptime(6500)
275 .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
Enrico Granataa97fce22017-10-27 14:57:34 -0700276
Enrico Granata7e0150d2017-11-06 17:20:17 -0800277 put("testBootIoStats",
278 new TestData(1000L,
279 new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
280 null,
281 new UidIoRecord[]{
282 new UidIoRecord(0, 5000, 6000, 3000, 1000, 1,
283 0, 0, 0, 0, 0),
284 new UidIoRecord(1000, 200, 5000, 0, 4000, 0,
285 1000, 0, 500, 0, 0)}));
Enrico Granata0f72b742017-11-02 18:26:41 -0700286
Enrico Granata7e0150d2017-11-06 17:20:17 -0800287 put("testAggregateIoStats",
288 new TestData(1000L,
289 new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
290 null,
291 new UidIoRecord[]{
292 new UidIoRecord(0, 5000, 6000, 3000, 1000, 1,
293 0, 0, 0, 0, 0),
294 new UidIoRecord(1000, 200, 5000, 0, 4000, 0,
295 1000, 0, 500, 0, 0)}));
Enrico Granata0f72b742017-11-02 18:26:41 -0700296
Enrico Granata7e0150d2017-11-06 17:20:17 -0800297 put("testIoStatsDeltas",
298 new TestData(1000L,
299 new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
300 null,
301 new UidIoRecord[]{
302 new UidIoRecord(0, 5000, 6000, 3000, 1000, 1,
303 0, 0, 0, 0, 0)}));
Enrico Granata1690a622018-01-22 17:34:46 -0800304
305 put("testComputeShutdownCost",
306 new TestData(1000L,
307 new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
308 null,
309 null,
310 new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 120),
311 new LifetimeWriteInfo("p2", "ext4", 100),
312 new LifetimeWriteInfo("p3", "f2fs", 100)},
313 new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 200),
314 new LifetimeWriteInfo("p2", "ext4", 300),
315 new LifetimeWriteInfo("p3", "f2fs", 100)}));
316
317 put("testNegativeShutdownCost",
318 new TestData(1000L,
319 new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
320 null,
321 null,
322 new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 120),
323 new LifetimeWriteInfo("p2", "ext4", 100),
324 new LifetimeWriteInfo("p3", "f2fs", 200)},
325 new LifetimeWriteInfo[] { new LifetimeWriteInfo("p1", "ext4", 200),
326 new LifetimeWriteInfo("p2", "ext4", 300),
327 new LifetimeWriteInfo("p3", "f2fs", 100)}));
Enrico Granata7e0150d2017-11-06 17:20:17 -0800328 }};
Enrico Granata1172f882017-09-21 14:51:30 -0700329
Enrico Granatab19bc322017-10-12 12:25:06 -0700330 private final MockSystemStateInterface mMockSystemStateInterface =
331 new MockSystemStateInterface();
332 private final MockStorageMonitoringInterface mMockStorageMonitoringInterface =
333 new MockStorageMonitoringInterface();
Enrico Granata0f72b742017-11-02 18:26:41 -0700334 private final MockTimeInterface mMockTimeInterface =
335 new MockTimeInterface();
Enrico Granata57738692018-02-23 15:35:07 -0800336 private TestContext mContext;
Enrico Granatab19bc322017-10-12 12:25:06 -0700337
Enrico Granata517a1e02017-09-20 16:15:50 -0700338 private CarStorageMonitoringManager mCarStorageMonitoringManager;
339
340 @Override
Enrico Granata57738692018-02-23 15:35:07 -0800341 protected MockContext getCarServiceContext() throws NameNotFoundException {
342 if (mContext == null) {
343 mContext = new TestContext(super.getCarServiceContext());
344 }
345 return mContext;
346 }
347
348 @Override
Enrico Granatab19bc322017-10-12 12:25:06 -0700349 protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() {
350 SystemInterface.Builder builder = super.getSystemInterfaceBuilder();
351 return builder.withSystemStateInterface(mMockSystemStateInterface)
352 .withStorageMonitoringInterface(mMockStorageMonitoringInterface)
Enrico Granata0f72b742017-11-02 18:26:41 -0700353 .withTimeInterface(mMockTimeInterface);
Enrico Granatab19bc322017-10-12 12:25:06 -0700354 }
355
356 @Override
Enrico Granata517a1e02017-09-20 16:15:50 -0700357 protected synchronized void configureFakeSystemInterface() {
Enrico Granata286cd8e2017-09-25 14:46:49 -0700358 try {
Pavel Maltseved2c8642017-12-18 12:56:26 -0800359 final TestData wearData = PER_TEST_DATA.getOrDefault(getName(), TestData.DEFAULT);
Enrico Granata286cd8e2017-09-25 14:46:49 -0700360 final WearHistory wearHistory = wearData.wearHistory;
361
Enrico Granatab19bc322017-10-12 12:25:06 -0700362 mMockStorageMonitoringInterface.setWearInformation(wearData.wearInformation);
Enrico Granata1690a622018-01-22 17:34:46 -0800363 mMockStorageMonitoringInterface.setWriteInfo(wearData.currentLifetimeWriteInfo);
Enrico Granata286cd8e2017-09-25 14:46:49 -0700364
365 if (wearHistory != null) {
366 File wearHistoryFile = new File(getFakeSystemInterface().getFilesDir(),
367 CarStorageMonitoringService.WEAR_INFO_FILENAME);
368 try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(wearHistoryFile))) {
369 wearHistory.writeToJson(jsonWriter);
370 }
371 }
372
373 if (wearData.uptime > 0) {
374 File uptimeFile = new File(getFakeSystemInterface().getFilesDir(),
Enrico Granatab19bc322017-10-12 12:25:06 -0700375 CarStorageMonitoringService.UPTIME_TRACKER_FILENAME);
Enrico Granata286cd8e2017-09-25 14:46:49 -0700376 try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(uptimeFile))) {
377 jsonWriter.beginObject();
378 jsonWriter.name("uptime").value(wearData.uptime);
379 jsonWriter.endObject();
380 }
381 }
Enrico Granataa97fce22017-10-27 14:57:34 -0700382
Enrico Granata1690a622018-01-22 17:34:46 -0800383 if (wearData.previousLifetimeWriteInfo.length > 0) {
384 File previousLifetimeFile = new File(getFakeSystemInterface().getFilesDir(),
385 CarStorageMonitoringService.LIFETIME_WRITES_FILENAME);
386 try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(previousLifetimeFile))) {
387 jsonWriter.beginObject();
388 jsonWriter.name("lifetimeWriteInfo").beginArray();
389 for (LifetimeWriteInfo writeInfo : wearData.previousLifetimeWriteInfo) {
390 writeInfo.writeToJson(jsonWriter);
391 }
392 jsonWriter.endArray().endObject();
393 }
394 }
395
Enrico Granataa97fce22017-10-27 14:57:34 -0700396 Arrays.stream(wearData.ioStats).forEach(
397 mMockStorageMonitoringInterface::addIoStatsRecord);
398
Enrico Granata286cd8e2017-09-25 14:46:49 -0700399 } catch (IOException e) {
400 Log.e(TAG, "failed to configure fake system interface", e);
401 fail("failed to configure fake system interface instance");
402 }
Enrico Granata43024de2017-11-16 17:09:37 -0800403 }
Enrico Granatab19bc322017-10-12 12:25:06 -0700404
Enrico Granata43024de2017-11-16 17:09:37 -0800405 @Override
406 protected synchronized void configureResourceOverrides(MockResources resources) {
Pavel Maltseved2c8642017-12-18 12:56:26 -0800407 final ResourceOverrides overrides = PER_TEST_RESOURCES.getOrDefault(getName(), null);
Enrico Granata43024de2017-11-16 17:09:37 -0800408 if (overrides != null) {
409 overrides.overrideResources(resources);
410 }
Enrico Granata517a1e02017-09-20 16:15:50 -0700411 }
412
413 @Override
Pavel Maltseved2c8642017-12-18 12:56:26 -0800414 public void setUp() throws Exception {
Enrico Granata517a1e02017-09-20 16:15:50 -0700415 super.setUp();
Enrico Granatab19bc322017-10-12 12:25:06 -0700416 mMockSystemStateInterface.executeBootCompletedActions();
Enrico Granatacf53fd72017-09-28 10:45:44 -0700417
Enrico Granata517a1e02017-09-20 16:15:50 -0700418 mCarStorageMonitoringManager =
419 (CarStorageMonitoringManager) getCar().getCarManager(Car.STORAGE_MONITORING_SERVICE);
420 }
421
Pavel Maltseved2c8642017-12-18 12:56:26 -0800422 @Test
Enrico Granata517a1e02017-09-20 16:15:50 -0700423 public void testReadPreEolInformation() throws Exception {
Enrico Granata1172f882017-09-21 14:51:30 -0700424 assertEquals(DEFAULT_WEAR_INFORMATION.preEolInfo,
Enrico Granata517a1e02017-09-20 16:15:50 -0700425 mCarStorageMonitoringManager.getPreEolIndicatorStatus());
426 }
Enrico Granata1172f882017-09-21 14:51:30 -0700427
Pavel Maltseved2c8642017-12-18 12:56:26 -0800428 @Test
Enrico Granata1172f882017-09-21 14:51:30 -0700429 public void testReadWearEstimate() throws Exception {
430 final WearEstimate wearEstimate = mCarStorageMonitoringManager.getWearEstimate();
431
432 assertNotNull(wearEstimate);
433 assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateA, wearEstimate.typeA);
434 assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateB, wearEstimate.typeB);
435 }
Enrico Granata286cd8e2017-09-25 14:46:49 -0700436
Pavel Maltseved2c8642017-12-18 12:56:26 -0800437 @Test
Enrico Granata286cd8e2017-09-25 14:46:49 -0700438 public void testReadWearHistory() throws Exception {
439 final List<WearEstimateChange> wearEstimateChanges =
440 mCarStorageMonitoringManager.getWearEstimateHistory();
441
442 assertNotNull(wearEstimateChanges);
443 assertFalse(wearEstimateChanges.isEmpty());
444
Enrico Granataa97fce22017-10-27 14:57:34 -0700445 final WearHistory expectedWearHistory = PER_TEST_DATA.get(getName()).wearHistory;
Enrico Granata286cd8e2017-09-25 14:46:49 -0700446
447 assertEquals(expectedWearHistory.size(), wearEstimateChanges.size());
448 for (int i = 0; i < wearEstimateChanges.size(); ++i) {
449 final WearEstimateRecord expected = expectedWearHistory.get(i);
450 final WearEstimateChange actual = wearEstimateChanges.get(i);
451
452 assertTrue(expected.isSameAs(actual));
453 }
454 }
455
456 private void checkLastWearEvent(boolean isAcceptable) throws Exception {
457 final List<WearEstimateChange> wearEstimateChanges =
458 mCarStorageMonitoringManager.getWearEstimateHistory();
459
460 assertNotNull(wearEstimateChanges);
461 assertFalse(wearEstimateChanges.isEmpty());
462
Enrico Granataa97fce22017-10-27 14:57:34 -0700463 final TestData wearData = PER_TEST_DATA.get(getName());
Enrico Granata286cd8e2017-09-25 14:46:49 -0700464
465 final WearInformation expectedCurrentWear = wearData.wearInformation;
466 final WearEstimate expectedPreviousWear = wearData.wearHistory.getLast().getNewWearEstimate();
467
468 final WearEstimateChange actualCurrentWear =
469 wearEstimateChanges.get(wearEstimateChanges.size() - 1);
470
471 assertEquals(isAcceptable, actualCurrentWear.isAcceptableDegradation);
472 assertEquals(expectedCurrentWear.toWearEstimate(), actualCurrentWear.newEstimate);
473 assertEquals(expectedPreviousWear, actualCurrentWear.oldEstimate);
474 }
475
Pavel Maltseved2c8642017-12-18 12:56:26 -0800476 @Test
Enrico Granata286cd8e2017-09-25 14:46:49 -0700477 public void testNotAcceptableWearEvent() throws Exception {
478 checkLastWearEvent(false);
479 }
480
Pavel Maltseved2c8642017-12-18 12:56:26 -0800481 @Test
Enrico Granata286cd8e2017-09-25 14:46:49 -0700482 public void testAcceptableWearEvent() throws Exception {
483 checkLastWearEvent(true);
484 }
Enrico Granatab19bc322017-10-12 12:25:06 -0700485
Pavel Maltseved2c8642017-12-18 12:56:26 -0800486 @Test
Enrico Granataa97fce22017-10-27 14:57:34 -0700487 public void testBootIoStats() throws Exception {
Enrico Granatadbda56f2017-12-18 11:53:22 -0800488 final List<IoStatsEntry> bootIoStats =
Enrico Granataa97fce22017-10-27 14:57:34 -0700489 mCarStorageMonitoringManager.getBootIoStats();
490
491 assertNotNull(bootIoStats);
492 assertFalse(bootIoStats.isEmpty());
493
Enrico Granata7e0150d2017-11-06 17:20:17 -0800494 final UidIoRecord[] bootIoRecords = PER_TEST_DATA.get(getName()).ioStats;
Enrico Granataa97fce22017-10-27 14:57:34 -0700495
496 bootIoStats.forEach(uidIoStats -> assertTrue(Arrays.stream(bootIoRecords).anyMatch(
497 ioRecord -> uidIoStats.representsSameMetrics(ioRecord))));
498 }
499
Pavel Maltseved2c8642017-12-18 12:56:26 -0800500 @Test
Enrico Granata0f72b742017-11-02 18:26:41 -0700501 public void testAggregateIoStats() throws Exception {
Enrico Granata7e0150d2017-11-06 17:20:17 -0800502 UidIoRecord oldRecord1000 = mMockStorageMonitoringInterface.getIoStatsRecord(1000);
Enrico Granata0f72b742017-11-02 18:26:41 -0700503
Enrico Granata7e0150d2017-11-06 17:20:17 -0800504 UidIoRecord newRecord1000 = new UidIoRecord(1000,
Enrico Granata0f72b742017-11-02 18:26:41 -0700505 oldRecord1000.foreground_rchar,
506 oldRecord1000.foreground_wchar + 50,
507 oldRecord1000.foreground_read_bytes,
508 oldRecord1000.foreground_write_bytes + 100,
509 oldRecord1000.foreground_fsync + 1,
510 oldRecord1000.background_rchar,
511 oldRecord1000.background_wchar,
512 oldRecord1000.background_read_bytes,
513 oldRecord1000.background_write_bytes,
514 oldRecord1000.background_fsync);
515
516 mMockStorageMonitoringInterface.addIoStatsRecord(newRecord1000);
517
Enrico Granata7e0150d2017-11-06 17:20:17 -0800518 UidIoRecord record2000 = new UidIoRecord(2000,
Enrico Granata0f72b742017-11-02 18:26:41 -0700519 1024,
520 2048,
521 0,
522 1024,
523 1,
524 0,
525 0,
526 0,
527 0,
528 0);
529
530 mMockStorageMonitoringInterface.addIoStatsRecord(record2000);
531
532 mMockTimeInterface.tick();
533
Enrico Granatadbda56f2017-12-18 11:53:22 -0800534 List<IoStatsEntry> aggregateIoStats = mCarStorageMonitoringManager.getAggregateIoStats();
Enrico Granata0f72b742017-11-02 18:26:41 -0700535
536 assertNotNull(aggregateIoStats);
537 assertFalse(aggregateIoStats.isEmpty());
538
539 aggregateIoStats.forEach(serviceIoStat -> {
Enrico Granata7e0150d2017-11-06 17:20:17 -0800540 UidIoRecord mockIoStat = mMockStorageMonitoringInterface.getIoStatsRecord(
Enrico Granata0f72b742017-11-02 18:26:41 -0700541 serviceIoStat.uid);
542
543 assertNotNull(mockIoStat);
544
545 assertTrue(serviceIoStat.representsSameMetrics(mockIoStat));
546 });
547 }
548
Pavel Maltseved2c8642017-12-18 12:56:26 -0800549 @Test
Enrico Granata7e0150d2017-11-06 17:20:17 -0800550 public void testIoStatsDeltas() throws Exception {
551 UidIoRecord oldRecord0 = mMockStorageMonitoringInterface.getIoStatsRecord(0);
552
553 UidIoRecord newRecord0 = new UidIoRecord(0,
554 oldRecord0.foreground_rchar,
555 oldRecord0.foreground_wchar + 100,
556 oldRecord0.foreground_read_bytes,
557 oldRecord0.foreground_write_bytes + 50,
558 oldRecord0.foreground_fsync,
559 oldRecord0.background_rchar,
560 oldRecord0.background_wchar,
561 oldRecord0.background_read_bytes + 100,
562 oldRecord0.background_write_bytes,
563 oldRecord0.background_fsync);
564
565 mMockStorageMonitoringInterface.addIoStatsRecord(newRecord0);
566 mMockTimeInterface.setUptime(500).tick();
567
Enrico Granatadbda56f2017-12-18 11:53:22 -0800568 List<IoStats> deltas = mCarStorageMonitoringManager.getIoStatsDeltas();
Enrico Granata7e0150d2017-11-06 17:20:17 -0800569 assertNotNull(deltas);
570 assertEquals(1, deltas.size());
571
Enrico Granatadbda56f2017-12-18 11:53:22 -0800572 IoStats delta0 = deltas.get(0);
Enrico Granata7e0150d2017-11-06 17:20:17 -0800573 assertNotNull(delta0);
574 assertEquals(500, delta0.getTimestamp());
575
Enrico Granatadbda56f2017-12-18 11:53:22 -0800576 List<IoStatsEntry> delta0Stats = delta0.getStats();
Enrico Granata7e0150d2017-11-06 17:20:17 -0800577 assertNotNull(delta0Stats);
578 assertEquals(1, delta0Stats.size());
579
Enrico Granatadbda56f2017-12-18 11:53:22 -0800580 IoStatsEntry deltaRecord0 = delta0Stats.get(0);
Enrico Granata7e0150d2017-11-06 17:20:17 -0800581
582 assertTrue(deltaRecord0.representsSameMetrics(newRecord0.delta(oldRecord0)));
583
584 UidIoRecord newerRecord0 = new UidIoRecord(0,
585 newRecord0.foreground_rchar + 200,
586 newRecord0.foreground_wchar + 10,
587 newRecord0.foreground_read_bytes,
588 newRecord0.foreground_write_bytes,
589 newRecord0.foreground_fsync,
590 newRecord0.background_rchar,
591 newRecord0.background_wchar + 100,
592 newRecord0.background_read_bytes,
593 newRecord0.background_write_bytes + 30,
594 newRecord0.background_fsync + 2);
595
596 mMockStorageMonitoringInterface.addIoStatsRecord(newerRecord0);
597 mMockTimeInterface.setUptime(1000).tick();
598
599 deltas = mCarStorageMonitoringManager.getIoStatsDeltas();
600 assertNotNull(deltas);
601 assertEquals(2, deltas.size());
602
603 delta0 = deltas.get(0);
604 assertNotNull(delta0);
605 assertEquals(500, delta0.getTimestamp());
606
607 delta0Stats = delta0.getStats();
608 assertNotNull(delta0Stats);
609 assertEquals(1, delta0Stats.size());
610
611 deltaRecord0 = delta0Stats.get(0);
612
613 assertTrue(deltaRecord0.representsSameMetrics(newRecord0.delta(oldRecord0)));
614
Enrico Granatadbda56f2017-12-18 11:53:22 -0800615 IoStats delta1 = deltas.get(1);
Enrico Granata7e0150d2017-11-06 17:20:17 -0800616 assertNotNull(delta1);
617 assertEquals(1000, delta1.getTimestamp());
618
Enrico Granatadbda56f2017-12-18 11:53:22 -0800619 List<IoStatsEntry> delta1Stats = delta1.getStats();
Enrico Granata7e0150d2017-11-06 17:20:17 -0800620 assertNotNull(delta1Stats);
621 assertEquals(1, delta1Stats.size());
622
623 deltaRecord0 = delta1Stats.get(0);
624
625 assertTrue(deltaRecord0.representsSameMetrics(newerRecord0.delta(newRecord0)));
626 }
627
Pavel Maltseved2c8642017-12-18 12:56:26 -0800628 @Test
Enrico Granata7dce3792017-11-09 14:13:28 -0800629 public void testEventDelivery() throws Exception {
630 final Duration eventDeliveryDeadline = Duration.ofSeconds(5);
631
632 UidIoRecord record = new UidIoRecord(0,
633 0,
634 100,
635 0,
636 75,
637 1,
638 0,
639 0,
640 0,
641 0,
642 0);
643
644 Listener listener1 = new Listener("listener1");
645 Listener listener2 = new Listener("listener2");
646
647 mCarStorageMonitoringManager.registerListener(listener1);
648 mCarStorageMonitoringManager.registerListener(listener2);
649
650 mMockStorageMonitoringInterface.addIoStatsRecord(record);
651 mMockTimeInterface.setUptime(500).tick();
652
653 assertTrue(listener1.waitForEvent(eventDeliveryDeadline));
654 assertTrue(listener2.waitForEvent(eventDeliveryDeadline));
655
Enrico Granatadbda56f2017-12-18 11:53:22 -0800656 IoStats event1 = listener1.reset();
657 IoStats event2 = listener2.reset();
Enrico Granata7dce3792017-11-09 14:13:28 -0800658
659 assertEquals(event1, event2);
660 event1.getStats().forEach(stats -> assertTrue(stats.representsSameMetrics(record)));
661
662 mCarStorageMonitoringManager.unregisterListener(listener1);
663
664 mMockTimeInterface.setUptime(600).tick();
665 assertFalse(listener1.waitForEvent(eventDeliveryDeadline));
666 assertTrue(listener2.waitForEvent(eventDeliveryDeadline));
667 }
668
Pavel Maltseved2c8642017-12-18 12:56:26 -0800669 @Test
Enrico Granata43024de2017-11-16 17:09:37 -0800670 public void testIntentOnExcessiveWrite() throws Exception {
671 assertNull(CarStorageMonitoringBroadcastReceiver.reset());
672
673 final Duration intentDeliveryDeadline = Duration.ofSeconds(5);
674
675 UidIoRecord record = new UidIoRecord(0,
676 0,
677 5120,
678 0,
679 5000,
680 1,
681 0,
682 7168,
683 0,
684 7000,
685 0);
686
687 mMockStorageMonitoringInterface.addIoStatsRecord(record);
688 mMockTimeInterface.setUptime(500).tick();
689
690 assertTrue(CarStorageMonitoringBroadcastReceiver.waitForIntent(intentDeliveryDeadline));
691 Intent deliveredIntent = CarStorageMonitoringBroadcastReceiver.reset();
692 assertNotNull(deliveredIntent);
693 assertEquals(CarStorageMonitoringManager.INTENT_EXCESSIVE_IO, deliveredIntent.getAction());
694 }
695
Pavel Maltseved2c8642017-12-18 12:56:26 -0800696 @Test
Enrico Granata43024de2017-11-16 17:09:37 -0800697 public void testIntentOnExcessiveFsync() throws Exception {
698 assertNull(CarStorageMonitoringBroadcastReceiver.reset());
699
700 final Duration intentDeliveryDeadline = Duration.ofSeconds(5);
701
702 UidIoRecord record = new UidIoRecord(0,
703 0,
704 0,
705 0,
706 0,
707 2,
708 0,
709 0,
710 0,
711 0,
712 3);
713
714 mMockStorageMonitoringInterface.addIoStatsRecord(record);
715 mMockTimeInterface.setUptime(500).tick();
716
717 assertTrue(CarStorageMonitoringBroadcastReceiver.waitForIntent(intentDeliveryDeadline));
718 Intent deliveredIntent = CarStorageMonitoringBroadcastReceiver.reset();
719 assertNotNull(deliveredIntent);
720 assertEquals(CarStorageMonitoringManager.INTENT_EXCESSIVE_IO, deliveredIntent.getAction());
721 }
722
Pavel Maltseved2c8642017-12-18 12:56:26 -0800723 @Test
Enrico Granata88d92c82017-11-30 16:47:15 -0800724 public void testZeroWindowDisablesCollection() throws Exception {
725 final Duration eventDeliveryDeadline = Duration.ofSeconds(5);
726
727 UidIoRecord record = new UidIoRecord(0,
728 0,
729 100,
730 0,
731 75,
732 1,
733 0,
734 0,
735 0,
736 0,
737 0);
738
739 Listener listener = new Listener("listener");
740
741 mMockStorageMonitoringInterface.addIoStatsRecord(record);
742 mMockTimeInterface.setUptime(500).tick();
743
744 assertFalse(listener.waitForEvent(eventDeliveryDeadline));
745
746 assertEquals(0, mCarStorageMonitoringManager.getIoStatsDeltas().size());
747 }
748
Enrico Granata1690a622018-01-22 17:34:46 -0800749 @Test
750 public void testComputeShutdownCost() throws Exception {
751 assertEquals(280, mCarStorageMonitoringManager.getShutdownDiskWriteAmount());
752 }
753
754 @Test
755 public void testNegativeShutdownCost() throws Exception {
756 assertEquals(CarStorageMonitoringManager.SHUTDOWN_COST_INFO_MISSING,
757 mCarStorageMonitoringManager.getShutdownDiskWriteAmount());
758 }
759
Pavel Maltseved2c8642017-12-18 12:56:26 -0800760 private String getName() {
761 return mTestName.getMethodName();
762 }
763
Enrico Granatadbda56f2017-12-18 11:53:22 -0800764 static final class Listener implements CarStorageMonitoringManager.IoStatsListener {
Enrico Granata7dce3792017-11-09 14:13:28 -0800765 private final String mName;
766 private final Object mSync = new Object();
767
Enrico Granatadbda56f2017-12-18 11:53:22 -0800768 private IoStats mLastEvent = null;
Enrico Granata7dce3792017-11-09 14:13:28 -0800769
770 Listener(String name) {
771 mName = name;
772 }
773
Enrico Granatadbda56f2017-12-18 11:53:22 -0800774 IoStats reset() {
Enrico Granata7dce3792017-11-09 14:13:28 -0800775 synchronized (mSync) {
Enrico Granatadbda56f2017-12-18 11:53:22 -0800776 IoStats lastEvent = mLastEvent;
Enrico Granata7dce3792017-11-09 14:13:28 -0800777 mLastEvent = null;
778 return lastEvent;
779 }
780 }
781
782 boolean waitForEvent(Duration duration) {
783 long start = SystemClock.elapsedRealtime();
784 long end = start + duration.toMillis();
785 synchronized (mSync) {
786 while (mLastEvent == null && SystemClock.elapsedRealtime() < end) {
787 try {
788 mSync.wait(10L);
789 } catch (InterruptedException e) {
790 // ignore
791 }
792 }
793 }
794
795 return (mLastEvent != null);
796 }
797
798 @Override
Enrico Granatadbda56f2017-12-18 11:53:22 -0800799 public void onSnapshot(IoStats event) {
Enrico Granata7dce3792017-11-09 14:13:28 -0800800 synchronized (mSync) {
801 Log.d(TAG, "listener " + mName + " received event " + event);
802 // We're going to hold a reference to this object
803 mLastEvent = event;
804 mSync.notify();
805 }
806 }
807
808 }
809
Enrico Granatab19bc322017-10-12 12:25:06 -0700810 static final class MockStorageMonitoringInterface implements StorageMonitoringInterface,
811 WearInformationProvider {
812 private WearInformation mWearInformation = null;
Enrico Granata7e0150d2017-11-06 17:20:17 -0800813 private SparseArray<UidIoRecord> mIoStats = new SparseArray<>();
Enrico Granataa97fce22017-10-27 14:57:34 -0700814 private UidIoStatsProvider mIoStatsProvider = () -> mIoStats;
Enrico Granata1690a622018-01-22 17:34:46 -0800815 private LifetimeWriteInfo[] mWriteInfo = new LifetimeWriteInfo[0];
Enrico Granatab19bc322017-10-12 12:25:06 -0700816
817 void setWearInformation(WearInformation wearInformation) {
818 mWearInformation = wearInformation;
819 }
820
Enrico Granata1690a622018-01-22 17:34:46 -0800821 void setWriteInfo(LifetimeWriteInfo[] writeInfo) {
822 mWriteInfo = writeInfo;
823 }
824
Enrico Granata7e0150d2017-11-06 17:20:17 -0800825 void addIoStatsRecord(UidIoRecord record) {
Enrico Granataa97fce22017-10-27 14:57:34 -0700826 mIoStats.append(record.uid, record);
827 }
828
Enrico Granata7e0150d2017-11-06 17:20:17 -0800829 UidIoRecord getIoStatsRecord(int uid) {
Enrico Granata0f72b742017-11-02 18:26:41 -0700830 return mIoStats.get(uid);
831 }
832
Enrico Granataa97fce22017-10-27 14:57:34 -0700833 void deleteIoStatsRecord(int uid) {
834 mIoStats.delete(uid);
835 }
836
Enrico Granatab19bc322017-10-12 12:25:06 -0700837 @Override
838 public WearInformation load() {
839 return mWearInformation;
840 }
841
842 @Override
Enrico Granata1690a622018-01-22 17:34:46 -0800843 public LifetimeWriteInfoProvider getLifetimeWriteInfoProvider() {
844 // cannot make this directly implement because Java does not allow
845 // overloading based on return type and there already is a method named
846 // load() in WearInformationProvider
847 return new LifetimeWriteInfoProvider() {
848 @Override
849 public LifetimeWriteInfo[] load() {
850 return mWriteInfo;
851 }
852 };
853 }
854
855 @Override
Enrico Granatab19bc322017-10-12 12:25:06 -0700856 public WearInformationProvider[] getFlashWearInformationProviders() {
857 return new WearInformationProvider[] {this};
858 }
Enrico Granataa97fce22017-10-27 14:57:34 -0700859
860 @Override
861 public UidIoStatsProvider getUidIoStatsProvider() {
862 return mIoStatsProvider;
863 }
Enrico Granatab19bc322017-10-12 12:25:06 -0700864 }
865
866 static final class MockTimeInterface implements TimeInterface {
Enrico Granata0f72b742017-11-02 18:26:41 -0700867 private final List<Pair<Runnable, Long>> mActionsList = new ArrayList<>();
Enrico Granata7e0150d2017-11-06 17:20:17 -0800868 private long mUptime = 0;
Enrico Granatab19bc322017-10-12 12:25:06 -0700869
870 @Override
871 public long getUptime(boolean includeDeepSleepTime) {
Enrico Granata7e0150d2017-11-06 17:20:17 -0800872 return mUptime;
Enrico Granatab19bc322017-10-12 12:25:06 -0700873 }
874
875 @Override
Enrico Granata0f72b742017-11-02 18:26:41 -0700876 public void scheduleAction(Runnable r, long delayMs) {
877 mActionsList.add(Pair.create(r, delayMs));
878 mActionsList.sort(Comparator.comparing(d -> d.second));
879 }
Enrico Granatab19bc322017-10-12 12:25:06 -0700880
881 @Override
Enrico Granata0f72b742017-11-02 18:26:41 -0700882 public void cancelAllActions() {
883 mActionsList.clear();
884 }
885
886 void tick() {
887 mActionsList.forEach(pair -> pair.first.run());
888 }
Enrico Granata7e0150d2017-11-06 17:20:17 -0800889
890 MockTimeInterface setUptime(long time) {
891 mUptime = time;
892 return this;
893 }
Enrico Granatab19bc322017-10-12 12:25:06 -0700894 }
895
896 static final class MockSystemStateInterface implements SystemStateInterface {
897 private final List<Pair<Runnable, Duration>> mActionsList = new ArrayList<>();
898
899 @Override
900 public void shutdown() {}
901
902 @Override
Steve Paik0f9fc002018-02-09 17:42:00 -0800903 public boolean enterDeepSleep(int wakeupTimeSec) {
904 return true;
905 }
Enrico Granatab19bc322017-10-12 12:25:06 -0700906
907 @Override
908 public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
909 mActionsList.add(Pair.create(action, delay));
910 mActionsList.sort(Comparator.comparing(d -> d.second));
911 }
912
913 void executeBootCompletedActions() {
914 for (Pair<Runnable, Duration> action : mActionsList) {
915 action.first.run();
916 }
917 }
918 }
Enrico Granata517a1e02017-09-20 16:15:50 -0700919}