1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tests.sdksandbox.host;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.app.sdksandbox.hosttestutils.AwaitUtils;
24 import android.app.sdksandbox.hosttestutils.DeviceSupportHostUtils;
25 import android.app.sdksandbox.hosttestutils.SecondaryUserUtils;
26 
27 import com.android.modules.utils.build.testing.DeviceSdkLevel;
28 import com.android.tradefed.device.DeviceNotAvailableException;
29 import com.android.tradefed.invoker.TestInformation;
30 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
31 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
32 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
33 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Ignore;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 
41 @RunWith(DeviceJUnit4ClassRunner.class)
42 public final class SdkSandboxLifecycleHostTest extends BaseHostJUnit4Test {
43 
44     private static final String APP_PACKAGE = "com.android.sdksandbox.app";
45     private static final String APP_APK = "SdkSandboxTestApp.apk";
46     private static final String APP_2_PACKAGE = "com.android.sdksandbox.app2";
47 
48     private static final String APP_SHARED_PACKAGE = "com.android.sdksandbox.shared.app1";
49     private static final String APP_SHARED_ACTIVITY = "SdkSandboxTestSharedActivity";
50     private static final String APP_SHARED_2_PACKAGE = "com.android.sdksandbox.shared.app2";
51 
52     private static final String APP_ACTIVITY = "SdkSandboxTestActivity";
53     private static final String APP_2_ACTIVITY = "SdkSandboxTestActivity2";
54     private static final String APP_2_EMPTY_ACTIVITY = "SdkSandboxEmptyActivity";
55 
56     private static final String CODE_APK = "TestCodeProvider.apk";
57     private static final String CODE_APK_2 = "TestCodeProvider2.apk";
58 
59     private static final String APP_2_PROCESS_NAME = "com.android.sdksandbox.processname";
60     private static final String APP_2_PROCESS_NAME_2 = "com.android.sdksandbox.emptyactivity";
61     private static final String SANDBOX_2_PROCESS_NAME = APP_2_PROCESS_NAME
62                                                             + "_sdk_sandbox";
63     /**
64      * process name for app1 is not defined and it takes the package name by default
65      */
66     private static final String SANDBOX_1_PROCESS_NAME = APP_PACKAGE + "_sdk_sandbox";
67 
68     private static final String SANDBOX_SHARED_1_PROCESS_NAME = APP_SHARED_PACKAGE + "_sdk_sandbox";
69     private static final String SANDBOX_SHARED_2_PROCESS_NAME =
70             APP_SHARED_2_PACKAGE + "_sdk_sandbox";
71 
72     private final SecondaryUserUtils mUserUtils = new SecondaryUserUtils(this);
73     private final DeviceSupportHostUtils mDeviceSupportUtils = new DeviceSupportHostUtils(this);
74 
75     private DeviceSdkLevel mDeviceSdkLevel;
76 
77     /** Root device for all tests. */
78     @BeforeClassWithInfo
beforeClassWithDevice(TestInformation testInfo)79     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
80         assertThat(testInfo.getDevice().enableAdbRoot()).isTrue();
81     }
82 
83     /** UnRoot device after all tests. */
84     @AfterClassWithInfo
afterClassWithDevice(TestInformation testInfo)85     public static void afterClassWithDevice(TestInformation testInfo) throws Exception {
86         testInfo.getDevice().disableAdbRoot();
87     }
88 
89     @Before
setUp()90     public void setUp() throws Exception {
91         assumeTrue("Device supports SdkSandbox", mDeviceSupportUtils.isSdkSandboxSupported());
92 
93         assertThat(getBuild()).isNotNull();
94         assertThat(getDevice()).isNotNull();
95 
96         mDeviceSdkLevel = new DeviceSdkLevel(getDevice());
97 
98         if (!getDevice().isPackageInstalled(APP_PACKAGE)) {
99             installPackage(APP_APK);
100         }
101     }
102 
103     @After
tearDown()104     public void tearDown() throws Exception {
105         mUserUtils.removeSecondaryUserIfNecessary();
106         cleanUpAppAndSandboxProcesses();
107     }
108 
109     @Test
testSdkSandboxIsDestroyedOnAppDestroy()110     public void testSdkSandboxIsDestroyedOnAppDestroy() throws Exception {
111         startActivity(APP_PACKAGE, APP_ACTIVITY);
112         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
113         assertThat(processDump).contains(APP_PACKAGE + '\n');
114         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
115 
116         killApp(APP_PACKAGE);
117         waitForProcessDeath(SANDBOX_1_PROCESS_NAME);
118         processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
119         assertThat(processDump).doesNotContain(APP_PACKAGE + '\n');
120         assertThat(processDump).doesNotContain(SANDBOX_1_PROCESS_NAME);
121 
122         // Wait 5 seconds to ensure that the sandbox has not restarted dying.
123         Thread.sleep(5000);
124         waitForProcessDeath(SANDBOX_1_PROCESS_NAME);
125     }
126 
127     @Test
testSdkSandboxIsCreatedPerApp()128     public void testSdkSandboxIsCreatedPerApp() throws Exception {
129         startActivity(APP_PACKAGE, APP_ACTIVITY);
130         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
131         assertThat(processDump).contains(APP_PACKAGE + '\n');
132         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
133 
134         startActivity(APP_2_PACKAGE, APP_2_ACTIVITY);
135         processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
136         assertThat(processDump).contains(APP_2_PROCESS_NAME + '\n');
137         assertThat(processDump).contains(SANDBOX_2_PROCESS_NAME);
138         assertThat(processDump).contains(APP_PACKAGE + '\n');
139         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
140 
141         killApp(APP_2_PACKAGE);
142         // Wait a bit to allow sandbox death
143         waitForProcessDeath(SANDBOX_2_PROCESS_NAME);
144         processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
145         assertThat(processDump).doesNotContain(APP_2_PROCESS_NAME + '\n');
146         assertThat(processDump).doesNotContain(SANDBOX_2_PROCESS_NAME);
147         assertThat(processDump).contains(APP_PACKAGE + '\n');
148         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
149     }
150 
151     @Test
testSdkSandboxIsKilledOnAppUninstall()152     public void testSdkSandboxIsKilledOnAppUninstall() throws Exception {
153         startActivity(APP_PACKAGE, APP_ACTIVITY);
154         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
155         assertThat(processDump).contains(APP_PACKAGE + '\n');
156         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
157 
158         uninstallPackage(APP_PACKAGE);
159         waitForProcessDeath(SANDBOX_1_PROCESS_NAME);
160         // Should no longer see app/sdk sandbox running
161         processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
162         assertThat(processDump).doesNotContain(APP_PACKAGE + '\n');
163         assertThat(processDump).doesNotContain(SANDBOX_1_PROCESS_NAME);
164     }
165 
166     @Ignore("b/275299487")
167     @Test
testSandboxIsCreatedPerUser()168     public void testSandboxIsCreatedPerUser() throws Exception {
169         assumeTrue(getDevice().isMultiUserSupported());
170 
171         int secondaryUserId = mUserUtils.createAndStartSecondaryUser();
172         installPackageAsUser(APP_APK, false, secondaryUserId);
173 
174         // Start app for the primary user
175         startActivity(APP_PACKAGE, APP_ACTIVITY);
176         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
177         assertThat(processDump).contains(APP_PACKAGE + '\n');
178         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
179 
180         mUserUtils.switchToSecondaryUser();
181 
182         // Should still see an app/sdk sandbox running.
183         processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
184         assertThat(processDump).contains(APP_PACKAGE + '\n');
185         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
186 
187         // Start the app for the secondary user.
188         startActivity(APP_PACKAGE, APP_ACTIVITY);
189         // There should be two instances of app and sandbox processes - one for each user.
190         assertThat(getProcessOccurrenceCount(APP_PACKAGE + '\n')).isEqualTo(2);
191         assertThat(getProcessOccurrenceCount(SANDBOX_1_PROCESS_NAME)).isEqualTo(2);
192 
193         // Kill the app process for the secondary user.
194         killApp(APP_PACKAGE + '\n');
195         // There should be one instance of app and sandbox process after kill
196         assertThat(getProcessOccurrenceCount(APP_PACKAGE + '\n')).isEqualTo(1);
197         assertThat(getProcessOccurrenceCount(SANDBOX_1_PROCESS_NAME)).isEqualTo(1);
198     }
199 
200     @Test
testSdkSandboxIsKilledOnLoadedSdkUpdate()201     public void testSdkSandboxIsKilledOnLoadedSdkUpdate() throws Exception {
202         startActivity(APP_PACKAGE, APP_ACTIVITY);
203 
204         // Should see app/sdk sandbox running
205         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
206         assertThat(processDump).contains(APP_PACKAGE + '\n');
207         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
208 
209         // Update package loaded by app
210         installPackage(CODE_APK, "-d");
211 
212         // SDK sandbox should be killed
213         waitForProcessDeath(SANDBOX_1_PROCESS_NAME);
214     }
215 
216     @Test
testSdkSandboxIsKilledForNonLoadedSdkUpdate()217     public void testSdkSandboxIsKilledForNonLoadedSdkUpdate() throws Exception {
218         // Have the app load the first SDK.
219         startActivity(APP_2_PACKAGE, APP_2_ACTIVITY);
220 
221         // Should see app/sdk sandbox running
222         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
223         assertThat(processDump).contains(APP_2_PROCESS_NAME + '\n');
224         assertThat(processDump).contains(SANDBOX_2_PROCESS_NAME);
225 
226         // Update package consumed by the app, but not loaded into the sandbox.
227         installPackage(CODE_APK_2, "-d");
228 
229         // SDK sandbox should be killed
230         waitForProcessDeath(SANDBOX_2_PROCESS_NAME);
231     }
232 
233     @Test
testAppsWithSharedUidCanLoadSameSdk()234     public void testAppsWithSharedUidCanLoadSameSdk() throws Exception {
235         startActivity(APP_SHARED_PACKAGE, APP_SHARED_ACTIVITY);
236         assertThat(runDeviceTests(APP_SHARED_2_PACKAGE,
237                 "com.android.sdksandbox.shared.app2.SdkSandboxTestSharedApp2",
238                 "testLoadSdkIsSuccessful")).isTrue();
239     }
240 
241     @Test
testAppsWithSharedUid_OneAppDies()242     public void testAppsWithSharedUid_OneAppDies() throws Exception {
243         startActivity(APP_SHARED_PACKAGE, APP_SHARED_ACTIVITY);
244         assertThat(runDeviceTests(APP_SHARED_2_PACKAGE,
245                 "com.android.sdksandbox.shared.app2.SdkSandboxTestSharedApp2",
246                 "testLoadSdkIsSuccessful")).isTrue();
247 
248         // APP_SHARED_2_PACKAGE dies after running device-side tests.
249         waitForProcessDeath(SANDBOX_SHARED_2_PROCESS_NAME);
250 
251         // For U+, the other sandbox should still be alive.
252         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
253         assertThat(processDump).contains(SANDBOX_SHARED_1_PROCESS_NAME);
254     }
255 
256     @Test
testAppOwnedSdkSandboxInterfaceRemoval_AppDies()257     public void testAppOwnedSdkSandboxInterfaceRemoval_AppDies() throws Exception {
258         startActivity(APP_SHARED_PACKAGE, APP_SHARED_ACTIVITY);
259         assertThat(
260                         runDeviceTests(
261                                 APP_SHARED_2_PACKAGE,
262                                 "com.android.sdksandbox.shared.app2.SdkSandboxTestSharedApp2",
263                                 "testRegisterAppOwedSdkSandboxInterfacesBeforeAppDeath"))
264                 .isTrue();
265 
266         // APP_SHARED_2_PACKAGE dies after running device-side tests.
267         waitForProcessDeath(SANDBOX_SHARED_2_PROCESS_NAME);
268         assertThat(
269                         runDeviceTests(
270                                 APP_SHARED_2_PACKAGE,
271                                 "com.android.sdksandbox.shared.app2.SdkSandboxTestSharedApp2",
272                                 "testGetAppOwedSdkSandboxInterfacesOnAppDeath"))
273                 .isTrue();
274     }
275 
276     @Test
testSandboxIsKilledWhenKillswitchEnabled()277     public void testSandboxIsKilledWhenKillswitchEnabled() throws Exception {
278         try {
279             getDevice()
280                     .executeShellCommand("device_config put adservices disable_sdk_sandbox false");
281             startActivity(APP_2_PACKAGE, APP_2_ACTIVITY);
282             String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
283             assertThat(processDump).contains(APP_2_PROCESS_NAME + '\n');
284             assertThat(processDump).contains(SANDBOX_2_PROCESS_NAME);
285 
286             getDevice()
287                     .executeShellCommand("device_config put adservices disable_sdk_sandbox true");
288             waitForProcessDeath(SANDBOX_2_PROCESS_NAME);
289             waitForProcessDeath(APP_2_PROCESS_NAME);
290 
291             processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
292             // In U+ the app should be killed when the sandbox is killed.
293             assertThat(processDump).doesNotContain(APP_2_PROCESS_NAME + '\n');
294             assertThat(processDump).doesNotContain(SANDBOX_2_PROCESS_NAME);
295         } finally {
296             getDevice().executeShellCommand("cmd sdk_sandbox set-state --enabled");
297         }
298     }
299 
300     @Test
testSpecificAppProcessIsKilledOnSandboxDeath()301     public void testSpecificAppProcessIsKilledOnSandboxDeath() throws Exception {
302         try {
303             getDevice()
304                     .executeShellCommand("device_config put adservices disable_sdk_sandbox false");
305 
306             // Start two activities running in two different processes for the same app. One
307             // activity loads an SDK while the other does nothing.
308             startActivity(APP_2_PACKAGE, APP_2_EMPTY_ACTIVITY);
309             startActivity(APP_2_PACKAGE, APP_2_ACTIVITY);
310             String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
311             assertThat(processDump).contains(APP_2_PROCESS_NAME + '\n');
312             assertThat(processDump).contains(APP_2_PROCESS_NAME_2 + '\n');
313             assertThat(processDump).contains(SANDBOX_2_PROCESS_NAME);
314 
315             final String initialAppProcessPid = getDevice().getProcessPid(APP_2_PROCESS_NAME);
316 
317             // Kill the sandbox.
318             getDevice()
319                     .executeShellCommand("device_config put adservices disable_sdk_sandbox true");
320             waitForProcessDeath(SANDBOX_2_PROCESS_NAME);
321             try {
322                 waitForProcessDeath(APP_2_PROCESS_NAME + '\n');
323             } catch (Exception e) {
324                 // If the app process has not died, it could have restarted as it was the top
325                 // activity. Verify that it is not the same process by checking the PID.
326                 final String finalAppProcessPid = getDevice().getProcessPid(APP_2_PROCESS_NAME);
327                 assertThat(finalAppProcessPid).isNotEqualTo(initialAppProcessPid);
328             }
329 
330             // Only the app process which loaded the SDK should die. The other app process should
331             // still be alive.
332             processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
333             assertThat(processDump).doesNotContain(APP_2_PROCESS_NAME + '\n');
334             assertThat(processDump).doesNotContain(SANDBOX_2_PROCESS_NAME);
335             assertThat(processDump).contains(APP_2_PROCESS_NAME_2 + '\n');
336 
337         } finally {
338             getDevice().executeShellCommand("cmd sdk_sandbox set-state --enabled");
339         }
340     }
341 
342     @Test
testBackgroundingAppReducesSandboxPriority()343     public void testBackgroundingAppReducesSandboxPriority() throws Exception {
344         startActivity(APP_PACKAGE, APP_ACTIVITY);
345 
346         // Should see app/sdk sandbox running
347         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
348         assertThat(processDump).contains(APP_PACKAGE + '\n');
349         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
350 
351         int sandboxOomScoreAdj1 = getOomScoreAdj(SANDBOX_1_PROCESS_NAME);
352         int appOomScoreAdj1 = getOomScoreAdj(APP_PACKAGE);
353         // Verify that the sandbox process has lower priority than the app process.
354         assertThat(sandboxOomScoreAdj1).isAtLeast(appOomScoreAdj1);
355 
356         // Navigate to home screen to send both apps to the background.
357         getDevice().executeShellCommand("input keyevent KEYCODE_HOME");
358 
359         // Wait for app to be backgrounded and unbinding of sandbox to complete.
360         Thread.sleep(5000);
361 
362         // Should see app/sdk sandbox running
363         processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
364         assertThat(processDump).contains(APP_PACKAGE + '\n');
365         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
366 
367         int sandboxOomScoreAdj2 = getOomScoreAdj(SANDBOX_1_PROCESS_NAME);
368         int appOomScoreAdj2 = getOomScoreAdj(APP_PACKAGE);
369         // The higher the oom adj score, the lower the priority of the process.
370         assertThat(sandboxOomScoreAdj2).isGreaterThan(sandboxOomScoreAdj1);
371         assertThat(appOomScoreAdj2).isGreaterThan(appOomScoreAdj1);
372 
373         if (mDeviceSdkLevel.isDeviceAtLeastV()) {
374             assertThat(sandboxOomScoreAdj2).isAtLeast(appOomScoreAdj2);
375 
376             // Start other apps to try to reduce the priority of the app.
377             startActivity(APP_2_PACKAGE, APP_2_ACTIVITY);
378             startActivity(APP_SHARED_PACKAGE, APP_SHARED_ACTIVITY);
379             Thread.sleep(2000);
380 
381             processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
382             assertThat(processDump).contains(APP_PACKAGE + '\n');
383             assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
384 
385             int sandboxOomScoreAdj3 = getOomScoreAdj(SANDBOX_1_PROCESS_NAME);
386             int appOomScoreAdj3 = getOomScoreAdj(APP_PACKAGE);
387             assertThat(appOomScoreAdj3).isAtLeast(appOomScoreAdj2);
388             assertThat(sandboxOomScoreAdj3).isAtLeast(sandboxOomScoreAdj2);
389             assertThat(sandboxOomScoreAdj3).isAtLeast(appOomScoreAdj3);
390         }
391     }
392 
393     @Test
testSandboxReconnectionAfterDeath()394     public void testSandboxReconnectionAfterDeath() throws Exception {
395         startActivity(APP_PACKAGE, APP_ACTIVITY);
396 
397         // Should see app/sdk sandbox running
398         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
399         assertThat(processDump).contains(APP_PACKAGE + '\n');
400         assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
401 
402         String initialSandboxPid = getDevice().getProcessPid(SANDBOX_1_PROCESS_NAME);
403         getDevice().executeShellCommand("kill -9 " + initialSandboxPid);
404 
405         Thread.sleep(5000);
406 
407         processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
408         assertThat(processDump).contains(APP_PACKAGE + '\n');
409         // The sandbox should not restart in U+.
410         assertThat(processDump).doesNotContain(SANDBOX_1_PROCESS_NAME);
411     }
412 
413     @Ignore("b/310160187")
414     @Test
testSdkSandboxProcessNameForSecondaryUser()415     public void testSdkSandboxProcessNameForSecondaryUser() throws Exception {
416         assumeTrue(getDevice().isMultiUserSupported());
417         String appApk2 = "SdkSandboxTestApp2.apk";
418 
419         int secondaryUserId = mUserUtils.createAndStartSecondaryUser();
420         mUserUtils.switchToSecondaryUser();
421         installPackageAsUser(appApk2, false, secondaryUserId);
422         startActivity(APP_2_PACKAGE, APP_2_ACTIVITY);
423         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
424         assertThat(processDump).contains(APP_2_PROCESS_NAME + '\n');
425         assertThat(processDump).contains(SANDBOX_2_PROCESS_NAME);
426     }
427 
startActivity(String pkg, String activity)428     private void startActivity(String pkg, String activity) throws Exception {
429         getDevice()
430                 .executeShellCommand(
431                         String.format(
432                                 "am start -W -n %s/.%s --user %d",
433                                 pkg, activity, getDevice().getCurrentUser()));
434 
435         // Check that the activity has started correctly by checking that its process has started.
436         // Depending on the test package configuration, the package may differ from the process
437         // name.
438         String expectedProcessName = pkg;
439         if (pkg.equals(APP_2_PACKAGE)) {
440             if (activity.equals(APP_2_ACTIVITY)) {
441                 expectedProcessName = APP_2_PROCESS_NAME;
442             } else {
443                 expectedProcessName = APP_2_PROCESS_NAME_2;
444             }
445         }
446         assertThat(
447                         getDevice()
448                                 .executeShellCommand(
449                                         String.format("ps -A | grep %s", expectedProcessName)))
450                 .isNotEmpty();
451     }
452 
killApp(String pkg)453     private void killApp(String pkg) throws Exception {
454         getDevice().executeShellCommand(String.format("am force-stop --user current %s", pkg));
455         waitForProcessDeath(pkg + '\n');
456     }
457 
458     // Get the number of running processes with the given process name.
getProcessOccurrenceCount(String processName)459     private int getProcessOccurrenceCount(String processName) throws Exception {
460         String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
461 
462         int count = 0;
463         int processOccurrenceIndex = processDump.indexOf(processName);
464         while (processOccurrenceIndex >= 0) {
465             count++;
466             processOccurrenceIndex = processDump.indexOf(processName, processOccurrenceIndex + 1);
467         }
468         return count;
469     }
470 
cleanUpAppAndSandboxProcesses()471     private void cleanUpAppAndSandboxProcesses() throws Exception {
472         for (String pkg :
473                 new String[] {
474                     APP_PACKAGE, APP_2_PACKAGE, APP_SHARED_PACKAGE, APP_SHARED_2_PACKAGE
475                 }) {
476             killApp(pkg);
477         }
478 
479         // Ensure no sandbox is currently running
480         for (String sandbox :
481                 new String[] {
482                     SANDBOX_1_PROCESS_NAME,
483                     SANDBOX_2_PROCESS_NAME,
484                     SANDBOX_SHARED_1_PROCESS_NAME,
485                     SANDBOX_SHARED_2_PROCESS_NAME
486                 }) {
487             waitForProcessDeath(sandbox);
488         }
489     }
490 
getOomScoreAdj(String processName)491     private int getOomScoreAdj(String processName) throws DeviceNotAvailableException {
492         String pid = getDevice().getProcessPid(processName);
493         String oomScoreAdj =
494                 getDevice().executeShellCommand("cat /proc/" + pid + "/oom_score_adj").trim();
495         return Integer.parseInt(oomScoreAdj);
496     }
497 
waitForProcessDeath(String processName)498     private void waitForProcessDeath(String processName) throws Exception {
499         AwaitUtils.waitFor(
500                 () -> {
501                     String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
502                     return !processDump.contains(processName);
503                 },
504                 "Process " + processName + " has not died.");
505     }
506 }
507