1 /*
2  * Copyright (C) 2021 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 package com.android.csuite.core;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertThrows;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.Mockito.when;
24 
25 import android.service.dropbox.DropBoxManagerServiceDumpProto;
26 
27 import com.android.csuite.core.DeviceUtils.DeviceTimestamp;
28 import com.android.csuite.core.DeviceUtils.DeviceUtilsException;
29 import com.android.csuite.core.DeviceUtils.DropboxEntry;
30 import com.android.tradefed.device.DeviceNotAvailableException;
31 import com.android.tradefed.device.DeviceRuntimeException;
32 import com.android.tradefed.device.ITestDevice;
33 import com.android.tradefed.util.CommandResult;
34 import com.android.tradefed.util.CommandStatus;
35 import com.android.tradefed.util.IRunUtil;
36 
37 import com.google.common.jimfs.Jimfs;
38 import com.google.protobuf.ByteString;
39 
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.JUnit4;
43 import org.mockito.ArgumentCaptor;
44 import org.mockito.ArgumentMatcher;
45 import org.mockito.Mockito;
46 
47 import java.io.IOException;
48 import java.nio.file.FileSystem;
49 import java.nio.file.Files;
50 import java.nio.file.Path;
51 import java.util.Arrays;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.Set;
55 import java.util.concurrent.atomic.AtomicBoolean;
56 import java.util.stream.Collectors;
57 
58 @RunWith(JUnit4.class)
59 public final class DeviceUtilsTest {
60     private ITestDevice mDevice = Mockito.mock(ITestDevice.class);
61     private IRunUtil mRunUtil = Mockito.mock(IRunUtil.class);
62     private final FileSystem mFileSystem =
63             Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix());
64     private static final String TEST_PACKAGE_NAME = "package.name";
65 
66     @Test
grantExternalStoragePermissions_commandFailed_doesNotThrow()67     public void grantExternalStoragePermissions_commandFailed_doesNotThrow() throws Exception {
68         ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
69         when(mDevice.executeShellV2Command(captor.capture()))
70                 .thenReturn(createFailedCommandResult());
71         DeviceUtils sut = createSubjectUnderTest();
72 
73         sut.grantExternalStoragePermissions(TEST_PACKAGE_NAME);
74 
75         assertThat(captor.getValue()).contains("MANAGE_EXTERNAL_STORAGE allow");
76     }
77 
78     @Test
isPackageInstalled_packageIsInstalled_returnsTrue()79     public void isPackageInstalled_packageIsInstalled_returnsTrue() throws Exception {
80         String packageName = "package.name";
81         when(mDevice.executeShellV2Command(Mockito.startsWith("pm list packages")))
82                 .thenReturn(
83                         createSuccessfulCommandResultWithStdout("\npackage:" + packageName + "\n"));
84         DeviceUtils sut = createSubjectUnderTest();
85 
86         boolean res = sut.isPackageInstalled(packageName);
87 
88         assertTrue(res);
89     }
90 
91     @Test
isPackageInstalled_packageIsNotInstalled_returnsFalse()92     public void isPackageInstalled_packageIsNotInstalled_returnsFalse() throws Exception {
93         String packageName = "package.name";
94         when(mDevice.executeShellV2Command(Mockito.startsWith("pm list packages")))
95                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
96         DeviceUtils sut = createSubjectUnderTest();
97 
98         boolean res = sut.isPackageInstalled(packageName);
99 
100         assertFalse(res);
101     }
102 
103     @Test
isPackageInstalled_commandFailed_throws()104     public void isPackageInstalled_commandFailed_throws() throws Exception {
105         when(mDevice.executeShellV2Command(Mockito.startsWith("pm list packages")))
106                 .thenReturn(createFailedCommandResult());
107         DeviceUtils sut = createSubjectUnderTest();
108 
109         assertThrows(DeviceUtilsException.class, () -> sut.isPackageInstalled("package.name"));
110     }
111 
112     @Test
launchPackage_pmDumpFailedAndPackageDoesNotExist_throws()113     public void launchPackage_pmDumpFailedAndPackageDoesNotExist_throws() throws Exception {
114         when(mDevice.executeShellV2Command(Mockito.startsWith("monkey")))
115                 .thenReturn(createFailedCommandResult());
116         when(mDevice.executeShellV2Command(Mockito.startsWith("pm dump")))
117                 .thenReturn(createFailedCommandResult());
118         when(mDevice.executeShellV2Command(Mockito.startsWith("pm list packages")))
119                 .thenReturn(createSuccessfulCommandResultWithStdout("no packages"));
120         DeviceUtils sut = createSubjectUnderTest();
121 
122         assertThrows(DeviceUtilsException.class, () -> sut.launchPackage("package.name"));
123     }
124 
125     @Test
launchPackage_pmDumpFailedAndPackageExists_throws()126     public void launchPackage_pmDumpFailedAndPackageExists_throws() throws Exception {
127         when(mDevice.executeShellV2Command(Mockito.startsWith("monkey")))
128                 .thenReturn(createFailedCommandResult());
129         when(mDevice.executeShellV2Command(Mockito.startsWith("pm dump")))
130                 .thenReturn(createFailedCommandResult());
131         when(mDevice.executeShellV2Command(Mockito.startsWith("pm list packages")))
132                 .thenReturn(createSuccessfulCommandResultWithStdout("package:package.name"));
133         DeviceUtils sut = createSubjectUnderTest();
134 
135         assertThrows(DeviceUtilsException.class, () -> sut.launchPackage("package.name"));
136     }
137 
138     @Test
launchPackage_amStartCommandFailed_throws()139     public void launchPackage_amStartCommandFailed_throws() throws Exception {
140         when(mDevice.executeShellV2Command(Mockito.startsWith("monkey")))
141                 .thenReturn(createFailedCommandResult());
142         when(mDevice.executeShellV2Command(Mockito.startsWith("pm dump")))
143                 .thenReturn(
144                         createSuccessfulCommandResultWithStdout(
145                                 "        87f1610"
146                                     + " com.google.android.gms/.app.settings.GoogleSettingsActivity"
147                                     + " filter 7357509\n"
148                                     + "          Action: \"android.intent.action.MAIN\"\n"
149                                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
150                                     + "          Category: \"android.intent.category.DEFAULT\"\n"
151                                     + "          Category:"
152                                     + " \"android.intent.category.NOTIFICATION_PREFERENCES\""));
153         when(mDevice.executeShellV2Command(Mockito.startsWith("am start")))
154                 .thenReturn(createFailedCommandResult());
155         DeviceUtils sut = createSubjectUnderTest();
156 
157         assertThrows(DeviceUtilsException.class, () -> sut.launchPackage("com.google.android.gms"));
158     }
159 
160     @Test
launchPackage_amFailedToLaunchThePackage_throws()161     public void launchPackage_amFailedToLaunchThePackage_throws() throws Exception {
162         when(mDevice.executeShellV2Command(Mockito.startsWith("monkey")))
163                 .thenReturn(createFailedCommandResult());
164         when(mDevice.executeShellV2Command(Mockito.startsWith("pm dump")))
165                 .thenReturn(
166                         createSuccessfulCommandResultWithStdout(
167                                 "        87f1610"
168                                     + " com.google.android.gms/.app.settings.GoogleSettingsActivity"
169                                     + " filter 7357509\n"
170                                     + "          Action: \"android.intent.action.MAIN\"\n"
171                                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
172                                     + "          Category: \"android.intent.category.DEFAULT\"\n"
173                                     + "          Category:"
174                                     + " \"android.intent.category.NOTIFICATION_PREFERENCES\""));
175         when(mDevice.executeShellV2Command(Mockito.startsWith("am start")))
176                 .thenReturn(
177                         createSuccessfulCommandResultWithStdout(
178                                 "Error: Activity not started, unable to resolve Intent"));
179         DeviceUtils sut = createSubjectUnderTest();
180 
181         assertThrows(DeviceUtilsException.class, () -> sut.launchPackage("com.google.android.gms"));
182     }
183 
184     @Test
launchPackage_monkeyFailedButAmSucceed_doesNotThrow()185     public void launchPackage_monkeyFailedButAmSucceed_doesNotThrow() throws Exception {
186         when(mDevice.executeShellV2Command(Mockito.startsWith("monkey")))
187                 .thenReturn(createFailedCommandResult());
188         when(mDevice.executeShellV2Command(Mockito.startsWith("pm dump")))
189                 .thenReturn(
190                         createSuccessfulCommandResultWithStdout(
191                                 "        87f1610"
192                                     + " com.google.android.gms/.app.settings.GoogleSettingsActivity"
193                                     + " filter 7357509\n"
194                                     + "          Action: \"android.intent.action.MAIN\"\n"
195                                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
196                                     + "          Category: \"android.intent.category.DEFAULT\"\n"
197                                     + "          Category:"
198                                     + " \"android.intent.category.NOTIFICATION_PREFERENCES\""));
199         when(mDevice.executeShellV2Command(Mockito.startsWith("am start")))
200                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
201         DeviceUtils sut = createSubjectUnderTest();
202 
203         sut.launchPackage("com.google.android.gms");
204     }
205 
206     @Test
launchPackage_monkeySucceed_doesNotThrow()207     public void launchPackage_monkeySucceed_doesNotThrow() throws Exception {
208         when(mDevice.executeShellV2Command(Mockito.startsWith("monkey")))
209                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
210         when(mDevice.executeShellV2Command(Mockito.startsWith("pm dump")))
211                 .thenReturn(createFailedCommandResult());
212         when(mDevice.executeShellV2Command(Mockito.startsWith("am start")))
213                 .thenReturn(createFailedCommandResult());
214         DeviceUtils sut = createSubjectUnderTest();
215 
216         sut.launchPackage("package.name");
217     }
218 
219     @Test
getLaunchActivity_oneActivityIsLauncherAndMainAndDefault_returnsIt()220     public void getLaunchActivity_oneActivityIsLauncherAndMainAndDefault_returnsIt()
221             throws Exception {
222         String pmDump =
223                 "        eecc562 com.google.android.gms/.bugreport.BugreportActivity filter"
224                     + " ac016f3\n"
225                     + "          Action: \"android.intent.action.MAIN\"\n"
226                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
227                     + "        87f1610 com.google.android.gms/.app.settings.GoogleSettingsActivity"
228                     + " filter 7357509\n"
229                     + "          Action: \"android.intent.action.MAIN\"\n"
230                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
231                     + "          Category: \"android.intent.category.DEFAULT\"\n"
232                     + "          Category: \"android.intent.category.NOTIFICATION_PREFERENCES\"\n"
233                     + "        28957f2 com.google.android.gms/.kids.SyncTailTrapperActivity filter"
234                     + " 83cbcc0\n"
235                     + "          Action: \"android.intent.action.MAIN\"\n"
236                     + "          Category: \"android.intent.category.HOME\"\n"
237                     + "          Category: \"android.intent.category.DEFAULT\"";
238         DeviceUtils sut = createSubjectUnderTest();
239 
240         String res = sut.getLaunchActivity(pmDump);
241 
242         assertThat(res).isEqualTo("com.google.android.gms/.app.settings.GoogleSettingsActivity");
243     }
244 
245     @Test
getLaunchActivity_oneActivityIsLauncherAndMain_returnsIt()246     public void getLaunchActivity_oneActivityIsLauncherAndMain_returnsIt() throws Exception {
247         String pmDump =
248                 "        eecc562 com.google.android.gms/.bugreport.BugreportActivity filter"
249                     + " ac016f3\n"
250                     + "          Action: \"android.intent.action.MAIN\"\n"
251                     + "        87f1610 com.google.android.gms/.app.settings.GoogleSettingsActivity"
252                     + " filter 7357509\n"
253                     + "          Action: \"android.intent.action.MAIN\"\n"
254                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
255                     + "          Category: \"android.intent.category.NOTIFICATION_PREFERENCES\"\n"
256                     + "        28957f2 com.google.android.gms/.kids.SyncTailTrapperActivity filter"
257                     + " 83cbcc0\n"
258                     + "          Action: \"android.intent.action.MAIN\"\n"
259                     + "          Category: \"android.intent.category.HOME\"\n"
260                     + "          Category: \"android.intent.category.DEFAULT\"\n"
261                     + "          mPriority=10, mOrder=0, mHasStaticPartialTypes=false,"
262                     + " mHasDynamicPartialTypes=false";
263         DeviceUtils sut = createSubjectUnderTest();
264 
265         String res = sut.getLaunchActivity(pmDump);
266 
267         assertThat(res).isEqualTo("com.google.android.gms/.app.settings.GoogleSettingsActivity");
268     }
269 
270     @Test
271     public void
getLaunchActivity_oneActivityIsLauncherAndOneActivityIsMain_returnsTheLauncherActivity()272             getLaunchActivity_oneActivityIsLauncherAndOneActivityIsMain_returnsTheLauncherActivity()
273                     throws Exception {
274         String pmDump =
275                 "        eecc562 com.google.android.gms/.bugreport.BugreportActivity filter"
276                     + " ac016f3\n"
277                     + "          Action: \"android.intent.action.MAIN\"\n"
278                     + "        87f1610 com.google.android.gms/.app.settings.GoogleSettingsActivity"
279                     + " filter 7357509\n"
280                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
281                     + "          Category: \"android.intent.category.NOTIFICATION_PREFERENCES\"\n"
282                     + "        28957f2 com.google.android.gms/.kids.SyncTailTrapperActivity filter"
283                     + " 83cbcc0\n"
284                     + "          Action: \"android.intent.action.MAIN\"\n"
285                     + "          Category: \"android.intent.category.HOME\"\n"
286                     + "          Category: \"android.intent.category.DEFAULT\"\n"
287                     + "          mPriority=10, mOrder=0, mHasStaticPartialTypes=false,"
288                     + " mHasDynamicPartialTypes=false";
289         DeviceUtils sut = createSubjectUnderTest();
290 
291         String res = sut.getLaunchActivity(pmDump);
292 
293         assertThat(res).isEqualTo("com.google.android.gms/.app.settings.GoogleSettingsActivity");
294     }
295 
296     @Test
getLaunchActivity_oneActivityIsMain_returnsIt()297     public void getLaunchActivity_oneActivityIsMain_returnsIt() throws Exception {
298         String pmDump =
299                 "        eecc562 com.google.android.gms/.bugreport.BugreportActivity filter"
300                     + " ac016f3\n"
301                     + "          Action: \"android.intent.action.MAIN\"\n"
302                     + "        87f1610 com.google.android.gms/.app.settings.GoogleSettingsActivity"
303                     + " filter 7357509\n"
304                     + "          Category: \"android.intent.category.NOTIFICATION_PREFERENCES\"\n"
305                     + "        28957f2 com.google.android.gms/.kids.SyncTailTrapperActivity filter"
306                     + " 83cbcc0\n"
307                     + "          Category: \"android.intent.category.HOME\"\n"
308                     + "          Category: \"android.intent.category.DEFAULT\"\n"
309                     + "          mPriority=10, mOrder=0, mHasStaticPartialTypes=false,"
310                     + " mHasDynamicPartialTypes=false";
311         DeviceUtils sut = createSubjectUnderTest();
312 
313         String res = sut.getLaunchActivity(pmDump);
314 
315         assertThat(res).isEqualTo("com.google.android.gms/.bugreport.BugreportActivity");
316     }
317 
318     @Test
getLaunchActivity_oneActivityIsLauncher_returnsIt()319     public void getLaunchActivity_oneActivityIsLauncher_returnsIt() throws Exception {
320         String pmDump =
321                 "        eecc562 com.google.android.gms/.bugreport.BugreportActivity filter"
322                     + " ac016f3\n"
323                     + "          Category: \"android.intent.category.LAUNCHER\"\n"
324                     + "        87f1610 com.google.android.gms/.app.settings.GoogleSettingsActivity"
325                     + " filter 7357509\n"
326                     + "          Action: \"android.intent.action.MAIN\"\n"
327                     + "          Category: \"android.intent.category.NOTIFICATION_PREFERENCES\"\n"
328                     + "        28957f2 com.google.android.gms/.kids.SyncTailTrapperActivity filter"
329                     + " 83cbcc0\n"
330                     + "          Category: \"android.intent.category.HOME\"\n"
331                     + "          Category: \"android.intent.category.DEFAULT\"\n"
332                     + "          mPriority=10, mOrder=0, mHasStaticPartialTypes=false,"
333                     + " mHasDynamicPartialTypes=false";
334         DeviceUtils sut = createSubjectUnderTest();
335 
336         String res = sut.getLaunchActivity(pmDump);
337 
338         assertThat(res).isEqualTo("com.google.android.gms/.bugreport.BugreportActivity");
339     }
340 
341     @Test
getLaunchActivity_noMainOrLauncherActivities_throws()342     public void getLaunchActivity_noMainOrLauncherActivities_throws() throws Exception {
343         String pmDump =
344                 "        eecc562 com.google.android.gms/.bugreport.BugreportActivity filter"
345                     + " ac016f3\n"
346                     + "          Category: \"android.intent.category.HOME\"\n"
347                     + "        87f1610 com.google.android.gms/.app.settings.GoogleSettingsActivity"
348                     + " filter 7357509\n"
349                     + "          Category: \"android.intent.category.NOTIFICATION_PREFERENCES\"\n"
350                     + "        28957f2 com.google.android.gms/.kids.SyncTailTrapperActivity filter"
351                     + " 83cbcc0\n"
352                     + "          Category: \"android.intent.category.HOME\"\n"
353                     + "          Category: \"android.intent.category.DEFAULT\"\n"
354                     + "          mPriority=10, mOrder=0, mHasStaticPartialTypes=false,"
355                     + " mHasDynamicPartialTypes=false";
356         DeviceUtils sut = createSubjectUnderTest();
357 
358         assertThrows(DeviceUtilsException.class, () -> sut.getLaunchActivity(pmDump));
359     }
360 
361     @Test
currentTimeMillis_deviceCommandFailed_throwsException()362     public void currentTimeMillis_deviceCommandFailed_throwsException() throws Exception {
363         DeviceUtils sut = createSubjectUnderTest();
364         when(mDevice.executeShellV2Command(Mockito.startsWith("echo")))
365                 .thenReturn(createFailedCommandResult());
366 
367         assertThrows(DeviceRuntimeException.class, () -> sut.currentTimeMillis());
368     }
369 
370     @Test
currentTimeMillis_unexpectedFormat_throwsException()371     public void currentTimeMillis_unexpectedFormat_throwsException() throws Exception {
372         DeviceUtils sut = createSubjectUnderTest();
373         when(mDevice.executeShellV2Command(Mockito.startsWith("echo")))
374                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
375 
376         assertThrows(DeviceRuntimeException.class, () -> sut.currentTimeMillis());
377     }
378 
379     @Test
currentTimeMillis_successful_returnsTime()380     public void currentTimeMillis_successful_returnsTime() throws Exception {
381         DeviceUtils sut = createSubjectUnderTest();
382         when(mDevice.executeShellV2Command(Mockito.startsWith("echo")))
383                 .thenReturn(createSuccessfulCommandResultWithStdout("123"));
384 
385         DeviceTimestamp result = sut.currentTimeMillis();
386 
387         assertThat(result.get()).isEqualTo(Long.parseLong("123"));
388     }
389 
390     @Test
runWithScreenRecording_recordingDidNotStart_jobIsExecuted()391     public void runWithScreenRecording_recordingDidNotStart_jobIsExecuted() throws Exception {
392         DeviceUtils sut = createSubjectUnderTest();
393         when(mRunUtil.runCmdInBackground(Mockito.argThat(contains("shell", "screenrecord"))))
394                 .thenReturn(Mockito.mock(Process.class));
395         when(mDevice.executeShellV2Command(Mockito.startsWith("ls")))
396                 .thenReturn(createFailedCommandResult());
397         AtomicBoolean executed = new AtomicBoolean(false);
398         DeviceUtils.RunnableThrowingDeviceNotAvailable job = () -> executed.set(true);
399 
400         sut.runWithScreenRecording(job, (video, time) -> {});
401 
402         assertThat(executed.get()).isTrue();
403     }
404 
405     @Test
runWithScreenRecording_recordCommandThrowsException_jobIsExecuted()406     public void runWithScreenRecording_recordCommandThrowsException_jobIsExecuted()
407             throws Exception {
408         when(mRunUtil.runCmdInBackground(Mockito.argThat(contains("shell", "screenrecord"))))
409                 .thenThrow(new IOException());
410         DeviceUtils sut = createSubjectUnderTest();
411         AtomicBoolean executed = new AtomicBoolean(false);
412         DeviceUtils.RunnableThrowingDeviceNotAvailable job = () -> executed.set(true);
413 
414         sut.runWithScreenRecording(job, (video, time) -> {});
415 
416         assertThat(executed.get()).isTrue();
417     }
418 
419     @Test
runWithScreenRecording_jobThrowsException_videoFileIsHandled()420     public void runWithScreenRecording_jobThrowsException_videoFileIsHandled() throws Exception {
421         when(mRunUtil.runCmdInBackground(Mockito.argThat(contains("shell", "screenrecord"))))
422                 .thenReturn(Mockito.mock(Process.class));
423         when(mDevice.executeShellV2Command(Mockito.startsWith("ls")))
424                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
425         DeviceUtils sut = createSubjectUnderTest();
426         DeviceUtils.RunnableThrowingDeviceNotAvailable job =
427                 () -> {
428                     throw new RuntimeException();
429                 };
430         AtomicBoolean handled = new AtomicBoolean(false);
431 
432         assertThrows(
433                 RuntimeException.class,
434                 () -> sut.runWithScreenRecording(job, (video, time) -> handled.set(true)));
435 
436         assertThat(handled.get()).isTrue();
437     }
438 
439     @Test
getSdkLevel_returnsSdkLevelInteger()440     public void getSdkLevel_returnsSdkLevelInteger() throws DeviceNotAvailableException {
441         DeviceUtils sut = createSubjectUnderTest();
442         int sdkLevel = 30;
443         when(mDevice.executeShellV2Command(Mockito.eq("getprop ro.build.version.sdk")))
444                 .thenReturn(createSuccessfulCommandResultWithStdout("" + sdkLevel));
445 
446         int result = sut.getSdkLevel();
447 
448         assertThat(result).isEqualTo(sdkLevel);
449     }
450 
451     @Test
getPackageVersionName_deviceCommandFailed_returnsUnknown()452     public void getPackageVersionName_deviceCommandFailed_returnsUnknown() throws Exception {
453         DeviceUtils sut = createSubjectUnderTest();
454         when(mDevice.executeShellV2Command(Mockito.endsWith("grep versionName")))
455                 .thenReturn(createFailedCommandResult());
456 
457         String result = sut.getPackageVersionName("any");
458 
459         assertThat(result).isEqualTo(DeviceUtils.UNKNOWN);
460     }
461 
462     @Test
getPackageVersionName_deviceCommandReturnsUnexpected_returnsUnknown()463     public void getPackageVersionName_deviceCommandReturnsUnexpected_returnsUnknown()
464             throws Exception {
465         DeviceUtils sut = createSubjectUnderTest();
466         when(mDevice.executeShellV2Command(Mockito.endsWith("grep versionName")))
467                 .thenReturn(
468                         createSuccessfulCommandResultWithStdout(
469                                 "unexpected " + DeviceUtils.VERSION_NAME_PREFIX));
470 
471         String result = sut.getPackageVersionName("any");
472 
473         assertThat(result).isEqualTo(DeviceUtils.UNKNOWN);
474     }
475 
476     @Test
getPackageVersionName_deviceCommandSucceed_returnsVersionName()477     public void getPackageVersionName_deviceCommandSucceed_returnsVersionName() throws Exception {
478         DeviceUtils sut = createSubjectUnderTest();
479         when(mDevice.executeShellV2Command(Mockito.endsWith("grep versionName")))
480                 .thenReturn(
481                         createSuccessfulCommandResultWithStdout(
482                                 " " + DeviceUtils.VERSION_NAME_PREFIX + "123"));
483 
484         String result = sut.getPackageVersionName("any");
485 
486         assertThat(result).isEqualTo("123");
487     }
488 
489     @Test
getPackageVersionCode_deviceCommandFailed_returnsUnknown()490     public void getPackageVersionCode_deviceCommandFailed_returnsUnknown() throws Exception {
491         DeviceUtils sut = createSubjectUnderTest();
492         when(mDevice.executeShellV2Command(Mockito.endsWith("grep versionCode")))
493                 .thenReturn(createFailedCommandResult());
494 
495         String result = sut.getPackageVersionCode("any");
496 
497         assertThat(result).isEqualTo(DeviceUtils.UNKNOWN);
498     }
499 
500     @Test
getPackageVersionCode_deviceCommandReturnsUnexpected_returnsUnknown()501     public void getPackageVersionCode_deviceCommandReturnsUnexpected_returnsUnknown()
502             throws Exception {
503         DeviceUtils sut = createSubjectUnderTest();
504         when(mDevice.executeShellV2Command(Mockito.endsWith("grep versionCode")))
505                 .thenReturn(
506                         createSuccessfulCommandResultWithStdout(
507                                 "unexpected " + DeviceUtils.VERSION_CODE_PREFIX));
508 
509         String result = sut.getPackageVersionCode("any");
510 
511         assertThat(result).isEqualTo(DeviceUtils.UNKNOWN);
512     }
513 
514     @Test
getPackageVersionCode_deviceCommandSucceed_returnVersionCode()515     public void getPackageVersionCode_deviceCommandSucceed_returnVersionCode() throws Exception {
516         DeviceUtils sut = createSubjectUnderTest();
517         when(mDevice.executeShellV2Command(Mockito.endsWith("grep versionCode")))
518                 .thenReturn(
519                         createSuccessfulCommandResultWithStdout(
520                                 " " + DeviceUtils.VERSION_CODE_PREFIX + "123"));
521 
522         String result = sut.getPackageVersionCode("any");
523 
524         assertThat(result).isEqualTo("123");
525     }
526 
527     @Test
isDropboxEntryFromPackageProcess_cmdlineMatched_returnsTrue()528     public void isDropboxEntryFromPackageProcess_cmdlineMatched_returnsTrue() throws Exception {
529         String dropboxEntryData = "Cmd line: com.app.package";
530         String packageName = "com.app.package";
531         DeviceUtils sut = createSubjectUnderTest();
532 
533         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
534 
535         assertThat(res).isTrue();
536     }
537 
538     @Test
isDropboxEntryFromPackageProcess_processMatched_returnsTrue()539     public void isDropboxEntryFromPackageProcess_processMatched_returnsTrue() throws Exception {
540         String dropboxEntryData = "Process: com.app.package";
541         String packageName = "com.app.package";
542         DeviceUtils sut = createSubjectUnderTest();
543 
544         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
545 
546         assertThat(res).isTrue();
547     }
548 
549     @Test
isDropboxEntryFromPackageProcess_processMatchedInLines_returnsTrue()550     public void isDropboxEntryFromPackageProcess_processMatchedInLines_returnsTrue()
551             throws Exception {
552         String dropboxEntryData = "line\nProcess: com.app.package\nline";
553         String packageName = "com.app.package";
554         DeviceUtils sut = createSubjectUnderTest();
555 
556         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
557 
558         assertThat(res).isTrue();
559     }
560 
561     @Test
isDropboxEntryFromPackageProcess_processNameFollowedByOtherChar_returnsTrue()562     public void isDropboxEntryFromPackageProcess_processNameFollowedByOtherChar_returnsTrue()
563             throws Exception {
564         String dropboxEntryData = "line\nProcess: com.app.package, (time)\nline";
565         String packageName = "com.app.package";
566         DeviceUtils sut = createSubjectUnderTest();
567 
568         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
569 
570         assertThat(res).isTrue();
571     }
572 
573     @Test
isDropboxEntryFromPackageProcess_processNameFollowedByDot_returnsFalse()574     public void isDropboxEntryFromPackageProcess_processNameFollowedByDot_returnsFalse()
575             throws Exception {
576         String dropboxEntryData = "line\nProcess: com.app.package.sub, (time)\nline";
577         String packageName = "com.app.package";
578         DeviceUtils sut = createSubjectUnderTest();
579 
580         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
581 
582         assertThat(res).isFalse();
583     }
584 
585     @Test
isDropboxEntryFromPackageProcess_processNameFollowedByColon_returnsTrue()586     public void isDropboxEntryFromPackageProcess_processNameFollowedByColon_returnsTrue()
587             throws Exception {
588         String dropboxEntryData = "line\nProcess: com.app.package:sub, (time)\nline";
589         String packageName = "com.app.package";
590         DeviceUtils sut = createSubjectUnderTest();
591 
592         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
593 
594         assertThat(res).isTrue();
595     }
596 
597     @Test
isDropboxEntryFromPackageProcess_processNameFollowedByUnderscore_returnsFalse()598     public void isDropboxEntryFromPackageProcess_processNameFollowedByUnderscore_returnsFalse()
599             throws Exception {
600         String dropboxEntryData = "line\nProcess: com.app.package_sub, (time)\nline";
601         String packageName = "com.app.package";
602         DeviceUtils sut = createSubjectUnderTest();
603 
604         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
605 
606         assertThat(res).isFalse();
607     }
608 
609     @Test
isDropboxEntryFromPackageProcess_doesNotContainPackageName_returnsFalse()610     public void isDropboxEntryFromPackageProcess_doesNotContainPackageName_returnsFalse()
611             throws Exception {
612         String dropboxEntryData = "line\n";
613         String packageName = "com.app.package";
614         DeviceUtils sut = createSubjectUnderTest();
615 
616         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
617 
618         assertThat(res).isFalse();
619     }
620 
621     @Test
isDropboxEntryFromPackageProcess_packageNameWithUnderscorePrefix_returnsFalse()622     public void isDropboxEntryFromPackageProcess_packageNameWithUnderscorePrefix_returnsFalse()
623             throws Exception {
624         String dropboxEntryData = "line\na_com.app.package\n";
625         String packageName = "com.app.package";
626         DeviceUtils sut = createSubjectUnderTest();
627 
628         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
629 
630         assertThat(res).isFalse();
631     }
632 
633     @Test
isDropboxEntryFromPackageProcess_packageNameWithUnderscorePostfix_returnsFalse()634     public void isDropboxEntryFromPackageProcess_packageNameWithUnderscorePostfix_returnsFalse()
635             throws Exception {
636         String dropboxEntryData = "line\ncom.app.package_a\n";
637         String packageName = "com.app.package";
638         DeviceUtils sut = createSubjectUnderTest();
639 
640         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
641 
642         assertThat(res).isFalse();
643     }
644 
645     @Test
isDropboxEntryFromPackageProcess_packageNameWithDotPrefix_returnsFalse()646     public void isDropboxEntryFromPackageProcess_packageNameWithDotPrefix_returnsFalse()
647             throws Exception {
648         String dropboxEntryData = "line\na.com.app.package\n";
649         String packageName = "com.app.package";
650         DeviceUtils sut = createSubjectUnderTest();
651 
652         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
653 
654         assertThat(res).isFalse();
655     }
656 
657     @Test
isDropboxEntryFromPackageProcess_packageNameWithDotPostfix_returnsFalse()658     public void isDropboxEntryFromPackageProcess_packageNameWithDotPostfix_returnsFalse()
659             throws Exception {
660         String dropboxEntryData = "line\ncom.app.package.a\n";
661         String packageName = "com.app.package";
662         DeviceUtils sut = createSubjectUnderTest();
663 
664         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
665 
666         assertThat(res).isFalse();
667     }
668 
669     @Test
isDropboxEntryFromPackageProcess_packageNameWithColonPostfix_returnsTrue()670     public void isDropboxEntryFromPackageProcess_packageNameWithColonPostfix_returnsTrue()
671             throws Exception {
672         String dropboxEntryData = "line\ncom.app.package:a\n";
673         String packageName = "com.app.package";
674         DeviceUtils sut = createSubjectUnderTest();
675 
676         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
677 
678         assertThat(res).isTrue();
679     }
680 
681     @Test
682     public void
isDropboxEntryFromPackageProcess_packageNameWithAcceptiblePrefixAndPostfix_returnsTrue()683             isDropboxEntryFromPackageProcess_packageNameWithAcceptiblePrefixAndPostfix_returnsTrue()
684                     throws Exception {
685         String dropboxEntryData = "line\ncom.app.package)\n";
686         String packageName = "com.app.package";
687         DeviceUtils sut = createSubjectUnderTest();
688 
689         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
690 
691         assertThat(res).isTrue();
692     }
693 
694     @Test
695     public void
isDropboxEntryFromPackageProcess_wrongProcessNameWithCorrectPackageName_returnsFalse()696             isDropboxEntryFromPackageProcess_wrongProcessNameWithCorrectPackageName_returnsFalse()
697                     throws Exception {
698         String dropboxEntryData = "line\nProcess: com.app.package_other\ncom.app.package";
699         String packageName = "com.app.package";
700         DeviceUtils sut = createSubjectUnderTest();
701 
702         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
703 
704         assertThat(res).isFalse();
705     }
706 
707     @Test
isDropboxEntryFromPackageProcess_MultipleProcessNamesWithOneMatching_returnsTrue()708     public void isDropboxEntryFromPackageProcess_MultipleProcessNamesWithOneMatching_returnsTrue()
709             throws Exception {
710         String dropboxEntryData =
711                 "line\n"
712                         + "Process: com.app.package_other\n"
713                         + "Process: com.app.package\n"
714                         + "Process: com.other";
715         String packageName = "com.app.package";
716         DeviceUtils sut = createSubjectUnderTest();
717 
718         boolean res = sut.isDropboxEntryFromPackageProcess(dropboxEntryData, packageName);
719 
720         assertThat(res).isTrue();
721     }
722 
723     @Test
getDropboxEntries_containsEntriesOutsideTimeRange_onlyReturnsNewEntries()724     public void getDropboxEntries_containsEntriesOutsideTimeRange_onlyReturnsNewEntries()
725             throws Exception {
726         DeviceUtils sut = Mockito.spy(createSubjectUnderTest());
727         DeviceTimestamp startTime = new DeviceTimestamp(1);
728         DeviceTimestamp endTime = new DeviceTimestamp(3);
729         Mockito.doAnswer(
730                         inv ->
731                                 List.of(
732                                         new DeviceUtils.DropboxEntry(
733                                                 0,
734                                                 DeviceUtils.DROPBOX_APP_CRASH_TAGS
735                                                         .toArray(
736                                                                 new String
737                                                                         [DeviceUtils
738                                                                                 .DROPBOX_APP_CRASH_TAGS
739                                                                                 .size()])[0],
740                                                 TEST_PACKAGE_NAME + " entry1"),
741                                         new DeviceUtils.DropboxEntry(
742                                                 2,
743                                                 DeviceUtils.DROPBOX_APP_CRASH_TAGS
744                                                         .toArray(
745                                                                 new String
746                                                                         [DeviceUtils
747                                                                                 .DROPBOX_APP_CRASH_TAGS
748                                                                                 .size()])[0],
749                                                 TEST_PACKAGE_NAME + " entry2"),
750                                         new DeviceUtils.DropboxEntry(
751                                                 100,
752                                                 DeviceUtils.DROPBOX_APP_CRASH_TAGS
753                                                         .toArray(
754                                                                 new String
755                                                                         [DeviceUtils
756                                                                                 .DROPBOX_APP_CRASH_TAGS
757                                                                                 .size()])[0],
758                                                 TEST_PACKAGE_NAME + " entry3")))
759                 .when(sut)
760                 .getDropboxEntries(DeviceUtils.DROPBOX_APP_CRASH_TAGS);
761 
762         String result =
763                 sut
764                         .getDropboxEntries(
765                                 DeviceUtils.DROPBOX_APP_CRASH_TAGS,
766                                 TEST_PACKAGE_NAME,
767                                 startTime,
768                                 endTime)
769                         .stream()
770                         .map(DropboxEntry::toString)
771                         .collect(Collectors.joining("\n"));
772 
773         assertThat(result).doesNotContain("entry1");
774         assertThat(result).contains("entry2");
775         assertThat(result).doesNotContain("entry3");
776     }
777 
778     @Test
getDropboxEntries_containsOtherProcessEntries_onlyReturnsPackageEntries()779     public void getDropboxEntries_containsOtherProcessEntries_onlyReturnsPackageEntries()
780             throws Exception {
781         DeviceUtils sut = Mockito.spy(createSubjectUnderTest());
782         DeviceTimestamp startTime = new DeviceTimestamp(1);
783         Mockito.doAnswer(
784                         inv ->
785                                 List.of(
786                                         new DeviceUtils.DropboxEntry(
787                                                 2,
788                                                 DeviceUtils.DROPBOX_APP_CRASH_TAGS
789                                                         .toArray(
790                                                                 new String
791                                                                         [DeviceUtils
792                                                                                 .DROPBOX_APP_CRASH_TAGS
793                                                                                 .size()])[0],
794                                                 "other.package" + " entry1"),
795                                         new DeviceUtils.DropboxEntry(
796                                                 2,
797                                                 DeviceUtils.DROPBOX_APP_CRASH_TAGS
798                                                         .toArray(
799                                                                 new String
800                                                                         [DeviceUtils
801                                                                                 .DROPBOX_APP_CRASH_TAGS
802                                                                                 .size()])[0],
803                                                 TEST_PACKAGE_NAME + " entry2")))
804                 .when(sut)
805                 .getDropboxEntries(DeviceUtils.DROPBOX_APP_CRASH_TAGS);
806 
807         String result =
808                 sut
809                         .getDropboxEntries(
810                                 DeviceUtils.DROPBOX_APP_CRASH_TAGS,
811                                 TEST_PACKAGE_NAME,
812                                 startTime,
813                                 null)
814                         .stream()
815                         .map(DropboxEntry::toString)
816                         .collect(Collectors.joining("\n"));
817 
818         assertThat(result).doesNotContain("entry1");
819         assertThat(result).contains("entry2");
820     }
821 
822     @Test
getDropboxEntries_noEntries_returnsEmptyList()823     public void getDropboxEntries_noEntries_returnsEmptyList() throws Exception {
824         DeviceUtils sut = createSubjectUnderTest();
825         when(mRunUtil.runTimedCmd(
826                         Mockito.anyLong(),
827                         Mockito.eq("sh"),
828                         Mockito.eq("-c"),
829                         Mockito.contains("dumpsys dropbox --proto")))
830                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
831 
832         List<DropboxEntry> result = sut.getDropboxEntries(Set.of(""));
833 
834         assertThat(result).isEmpty();
835     }
836 
837     @Test
getDropboxEntries_entryExists_returnsEntry()838     public void getDropboxEntries_entryExists_returnsEntry() throws Exception {
839         Path dumpFile = Files.createTempFile(mFileSystem.getPath("/"), "dropbox", ".proto");
840         long time = 123;
841         String data = "abc";
842         String tag = "tag";
843         DropBoxManagerServiceDumpProto proto =
844                 DropBoxManagerServiceDumpProto.newBuilder()
845                         .addEntries(
846                                 DropBoxManagerServiceDumpProto.Entry.newBuilder()
847                                         .setTimeMs(time)
848                                         .setData(ByteString.copyFromUtf8(data)))
849                         .build();
850         Files.write(dumpFile, proto.toByteArray());
851         DeviceUtils sut = createSubjectUnderTestWithTempFile(dumpFile);
852         when(mRunUtil.runTimedCmd(
853                         Mockito.anyLong(),
854                         Mockito.eq("sh"),
855                         Mockito.eq("-c"),
856                         Mockito.contains("dumpsys dropbox --proto")))
857                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
858 
859         List<DropboxEntry> result = sut.getDropboxEntries(Set.of(tag));
860 
861         assertThat(result.get(0).getTime()).isEqualTo(time);
862         assertThat(result.get(0).getData()).isEqualTo(data);
863         assertThat(result.get(0).getTag()).isEqualTo(tag);
864     }
865 
866     @Test
getDropboxEntriesFromStdout_entryExists_returnsEntry()867     public void getDropboxEntriesFromStdout_entryExists_returnsEntry() throws Exception {
868         when(mRunUtil.runTimedCmd(
869                         Mockito.anyLong(),
870                         Mockito.eq("sh"),
871                         Mockito.eq("-c"),
872                         Mockito.contains("dumpsys dropbox --file")))
873                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
874         when(mRunUtil.runTimedCmd(
875                         Mockito.anyLong(),
876                         Mockito.eq("sh"),
877                         Mockito.eq("-c"),
878                         Mockito.contains("dumpsys dropbox --print")))
879                 .thenReturn(createSuccessfulCommandResultWithStdout(""));
880         Path fileDumpFile = Files.createTempFile(mFileSystem.getPath("/"), "file", ".dump");
881         Path printDumpFile = Files.createTempFile(mFileSystem.getPath("/"), "print", ".dump");
882         String fileResult =
883                 "Drop box contents: 351 entries\n"
884                         + "Max entries: 1000\n"
885                         + "Low priority rate limit period: 2000 ms\n"
886                         + "Low priority tags: {data_app_wtf, keymaster, system_server_wtf,"
887                         + " system_app_strictmode, system_app_wtf, system_server_strictmode,"
888                         + " data_app_strictmode, netstats}\n"
889                         + "\n"
890                         + "2022-09-05 04:17:21 system_server_wtf (text, 1730 bytes)\n"
891                         + "    /data/system/dropbox/system_server_wtf@1662351441269.txt\n"
892                         + "2022-09-05 04:31:06 event_data (text, 39 bytes)\n"
893                         + "    /data/system/dropbox/event_data@1662352266197.txt\n";
894         String printResult =
895                 "Drop box contents: 351 entries\n"
896                     + "Max entries: 1000\n"
897                     + "Low priority rate limit period: 2000 ms\n"
898                     + "Low priority tags: {data_app_wtf, keymaster, system_server_wtf,"
899                     + " system_app_strictmode, system_app_wtf, system_server_strictmode,"
900                     + " data_app_strictmode, netstats}\n"
901                     + "\n"
902                     + "========================================\n"
903                     + "2022-09-05 04:17:21 system_server_wtf (text, 1730 bytes)\n"
904                     + "Process: system_server\n"
905                     + "Subject: ActivityManager\n"
906                     + "Build:"
907                     + " generic/cf_x86_64_phone/vsoc_x86_64:UpsideDownCake/MASTER/8990215:userdebug/dev-keys\n"
908                     + "Dropped-Count: 0\n"
909                     + "\n"
910                     + "android.util.Log$TerribleFailure: Sending non-protected broadcast"
911                     + " com.android.bluetooth.btservice.BLUETOOTH_COUNTER_METRICS_ACTION from"
912                     + " system uid 1002 pkg com.android.bluetooth\n"
913                     + "    at android.util.Log.wtf(Log.java:332)\n"
914                     + "    at android.util.Log.wtf(Log.java:326)\n"
915                     + "    at"
916                     + " com.android.server.am.ActivityManagerService.checkBroadcastFromSystem(ActivityManagerService.java:13609)\n"
917                     + "    at"
918                     + " com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:14330)\n"
919                     + "    at"
920                     + " com.android.server.am.ActivityManagerService.broadcastIntentInPackage(ActivityManagerService.java:14530)\n"
921                     + "    at"
922                     + " com.android.server.am.ActivityManagerService$LocalService.broadcastIntentInPackage(ActivityManagerService.java:17065)\n"
923                     + "    at"
924                     + " com.android.server.am.PendingIntentRecord.sendInner(PendingIntentRecord.java:526)\n"
925                     + "    at"
926                     + " com.android.server.am.PendingIntentRecord.sendWithResult(PendingIntentRecord.java:311)\n"
927                     + "    at"
928                     + " com.android.server.am.ActivityManagerService.sendIntentSender(ActivityManagerService.java:5379)\n"
929                     + "    at"
930                     + " android.app.PendingIntent.sendAndReturnResult(PendingIntent.java:1012)\n"
931                     + "    at android.app.PendingIntent.send(PendingIntent.java:983)\n"
932                     + "    at"
933                     + " com.android.server.alarm.AlarmManagerService$DeliveryTracker.deliverLocked(AlarmManagerService.java:5500)\n"
934                     + "    at"
935                     + " com.android.server.alarm.AlarmManagerService.deliverAlarmsLocked(AlarmManagerService.java:4400)\n"
936                     + "    at"
937                     + " com.android.server.alarm.AlarmManagerService$AlarmThread.run(AlarmManagerService.java:4711)\n"
938                     + "Caused by: java.lang.Throwable\n"
939                     + "    at"
940                     + " com.android.server.am.ActivityManagerService.checkBroadcastFromSystem(ActivityManagerService.java:13610)\n"
941                     + "    ... 11 more\n"
942                     + "\n"
943                     + "========================================\n"
944                     + "2022-09-05 04:31:06 event_data (text, 39 bytes)\n"
945                     + "start=1662350731248\n"
946                     + "end=1662352266140\n"
947                     + "\n";
948         Files.write(fileDumpFile, fileResult.getBytes());
949         Files.write(printDumpFile, printResult.getBytes());
950         DeviceUtils sut = createSubjectUnderTestWithTempFile(fileDumpFile, printDumpFile);
951 
952         List<DropboxEntry> result = sut.getDropboxEntriesFromStdout(Set.of("system_server_wtf"));
953 
954         assertThat(result.get(0).getTime()).isEqualTo(1662351441269L);
955         assertThat(result.get(0).getData()).contains("Sending non-protected broadcast");
956         assertThat(result.get(0).getTag()).isEqualTo("system_server_wtf");
957         assertThat(result.size()).isEqualTo(1);
958     }
959 
createSubjectUnderTestWithTempFile(Path... tempFiles)960     private DeviceUtils createSubjectUnderTestWithTempFile(Path... tempFiles) {
961         when(mDevice.getSerialNumber()).thenReturn("SERIAL");
962         FakeClock fakeClock = new FakeClock();
963         Iterator<Path> iter = Arrays.asList(tempFiles).iterator();
964         return new DeviceUtils(
965                 mDevice, fakeClock.getSleeper(), fakeClock, () -> mRunUtil, () -> iter.next());
966     }
967 
createSubjectUnderTest()968     private DeviceUtils createSubjectUnderTest() throws DeviceNotAvailableException {
969         when(mDevice.getSerialNumber()).thenReturn("SERIAL");
970         when(mDevice.executeShellV2Command(Mockito.startsWith("echo ${EPOCHREALTIME")))
971                 .thenReturn(createSuccessfulCommandResultWithStdout("1"));
972         when(mDevice.executeShellV2Command(Mockito.eq("getprop ro.build.version.sdk")))
973                 .thenReturn(createSuccessfulCommandResultWithStdout("34"));
974         FakeClock fakeClock = new FakeClock();
975         return new DeviceUtils(
976                 mDevice,
977                 fakeClock.getSleeper(),
978                 fakeClock,
979                 () -> mRunUtil,
980                 () -> Files.createTempFile(mFileSystem.getPath("/"), "test", ".tmp"));
981     }
982 
983     private static class FakeClock implements DeviceUtils.Clock {
984         private long mCurrentTime = System.currentTimeMillis();
985         private DeviceUtils.Sleeper mSleeper = duration -> mCurrentTime += duration;
986 
getSleeper()987         private DeviceUtils.Sleeper getSleeper() {
988             return mSleeper;
989         }
990 
991         @Override
currentTimeMillis()992         public long currentTimeMillis() {
993             return mCurrentTime += 1;
994         }
995     }
996 
contains(String... args)997     private static ArgumentMatcher<String[]> contains(String... args) {
998         return array -> Arrays.asList(array).containsAll(Arrays.asList(args));
999     }
1000 
createSuccessfulCommandResultWithStdout(String stdout)1001     private static CommandResult createSuccessfulCommandResultWithStdout(String stdout) {
1002         CommandResult commandResult = new CommandResult(CommandStatus.SUCCESS);
1003         commandResult.setExitCode(0);
1004         commandResult.setStdout(stdout);
1005         commandResult.setStderr("");
1006         return commandResult;
1007     }
1008 
createFailedCommandResult()1009     private static CommandResult createFailedCommandResult() {
1010         CommandResult commandResult = new CommandResult(CommandStatus.FAILED);
1011         commandResult.setExitCode(1);
1012         commandResult.setStdout("");
1013         commandResult.setStderr("error");
1014         return commandResult;
1015     }
1016 }
1017