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 
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.app.sdksandbox.hosttestutils.AwaitUtils;
25 import android.app.sdksandbox.hosttestutils.DeviceSupportHostUtils;
26 
27 import com.android.tradefed.invoker.TestInformation;
28 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
29 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
30 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
31 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
32 import com.android.tradefed.util.CommandResult;
33 import com.android.tradefed.util.CommandStatus;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.util.HashSet;
41 
42 @RunWith(DeviceJUnit4ClassRunner.class)
43 public final class SdkSandboxShellHostTest extends BaseHostJUnit4Test {
44 
45     private static final String DEBUGGABLE_APP_PACKAGE = "com.android.sdksandbox.debuggable";
46     private static final String DEBUGGABLE_APP_ACTIVITY = "SdkSandboxTestDebuggableActivity";
47 
48     private static final String APP_PACKAGE = "com.android.sdksandbox.app";
49     private static final String APP_ACTIVITY = "SdkSandboxTestActivity";
50 
51     private static final String DEBUGGABLE_APP_SANDBOX_NAME = DEBUGGABLE_APP_PACKAGE
52       + "_sdk_sandbox";
53     private static final String APP_SANDBOX_NAME = APP_PACKAGE + "_sdk_sandbox";
54     private final DeviceSupportHostUtils mDeviceSupportUtils = new DeviceSupportHostUtils(this);
55     private final HashSet<Integer> mOriginalUsers = new HashSet<>();
56 
57     /** Root device for all tests. */
58     @BeforeClassWithInfo
beforeClassWithDevice(TestInformation testInfo)59     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
60         assertThat(testInfo.getDevice().enableAdbRoot()).isTrue();
61     }
62 
63     /** UnRoot device after all tests. */
64     @AfterClassWithInfo
afterClassWithDevice(TestInformation testInfo)65     public static void afterClassWithDevice(TestInformation testInfo) throws Exception {
66         testInfo.getDevice().disableAdbRoot();
67     }
68 
69     @Before
setUp()70     public void setUp() throws Exception {
71         assumeTrue("Device supports SdkSandbox", mDeviceSupportUtils.isSdkSandboxSupported());
72 
73         assertThat(getBuild()).isNotNull();
74         assertThat(getDevice()).isNotNull();
75 
76         // Ensure neither app is currently running
77         for (String pkg : new String[]{APP_PACKAGE, DEBUGGABLE_APP_PACKAGE}) {
78             clearProcess(pkg);
79             getDevice().executeShellV2Command(String.format("cmd deviceidle whitelist +%s", pkg));
80         }
81 
82         mOriginalUsers.addAll(getDevice().listUsers());
83     }
84 
85     @After
tearDown()86     public void tearDown() throws Exception {
87         for (Integer userId : getDevice().listUsers()) {
88             if (!mOriginalUsers.contains(userId)) {
89                 getDevice().removeUser(userId);
90             }
91         }
92         mOriginalUsers.clear();
93 
94         // Ensure all apps are not in allowlist.
95         for (String pkg : new String[] {APP_PACKAGE, DEBUGGABLE_APP_PACKAGE}) {
96             getDevice().executeShellV2Command(String.format("cmd deviceidle whitelist -%s", pkg));
97         }
98     }
99 
100     @Test
testStartAndStopSdkSandboxSucceedsForDebuggableApp()101     public void testStartAndStopSdkSandboxSucceedsForDebuggableApp() throws Exception {
102         CommandResult output = getDevice().executeShellV2Command(
103                 String.format("cmd sdk_sandbox start %s", DEBUGGABLE_APP_PACKAGE));
104         assertThat(output.getStderr()).isEmpty();
105         assertThat(output.getStatus()).isEqualTo(CommandStatus.SUCCESS);
106 
107         waitForProcessStart(DEBUGGABLE_APP_SANDBOX_NAME);
108 
109         output = getDevice().executeShellV2Command(
110                 String.format("cmd sdk_sandbox stop %s", DEBUGGABLE_APP_PACKAGE));
111         assertThat(output.getStderr()).isEmpty();
112         assertThat(output.getStatus()).isEqualTo(CommandStatus.SUCCESS);
113 
114         waitForProcessDeath(DEBUGGABLE_APP_SANDBOX_NAME);
115     }
116 
117     @Test
testStartSdkSandboxFailsForNonDebuggableApp()118     public void testStartSdkSandboxFailsForNonDebuggableApp() throws Exception {
119         CommandResult output = getDevice().executeShellV2Command(
120                 String.format("cmd sdk_sandbox start %s", APP_PACKAGE));
121         assertThat(output.getStatus()).isEqualTo(CommandStatus.FAILED);
122 
123         String processDump = getDevice().executeShellCommand("ps -A");
124         assertThat(processDump).doesNotContain(APP_SANDBOX_NAME);
125     }
126 
127     @Test
testStartSdkSandboxFailsForIncorrectUser()128     public void testStartSdkSandboxFailsForIncorrectUser() throws Exception {
129         int otherUserId = getDevice().createUser("TestUser_" + System.currentTimeMillis());
130         CommandResult output = getDevice().executeShellV2Command(
131                 String.format("cmd sdk_sandbox start --user %s %s",
132                         otherUserId, DEBUGGABLE_APP_PACKAGE));
133         assertThat(output.getStatus()).isEqualTo(CommandStatus.FAILED);
134 
135         String processDump = getDevice().executeShellCommand("ps -A");
136         assertThat(processDump).doesNotContain(DEBUGGABLE_APP_SANDBOX_NAME);
137     }
138 
139     @Test
testStopSdkSandboxSucceedsForRunningDebuggableApp()140     public void testStopSdkSandboxSucceedsForRunningDebuggableApp() throws Exception {
141         startActivity(DEBUGGABLE_APP_PACKAGE, DEBUGGABLE_APP_ACTIVITY);
142         waitForProcessStart(DEBUGGABLE_APP_SANDBOX_NAME);
143 
144         CommandResult output = getDevice().executeShellV2Command(
145                 String.format("cmd sdk_sandbox stop %s", DEBUGGABLE_APP_PACKAGE));
146         assertThat(output.getStderr()).isEmpty();
147         assertThat(output.getStatus()).isEqualTo(CommandStatus.SUCCESS);
148 
149         waitForProcessDeath(DEBUGGABLE_APP_SANDBOX_NAME);
150     }
151 
152     @Test
testStartSdkSandboxFailsForInvalidPackage()153     public void testStartSdkSandboxFailsForInvalidPackage() throws Exception {
154         String invalidPackage = "com.android.sdksandbox.nonexistent";
155         CommandResult output = getDevice().executeShellV2Command(
156                 String.format("cmd sdk_sandbox start %s", invalidPackage));
157         assertThat(output.getStatus()).isEqualTo(CommandStatus.FAILED);
158     }
159 
160     @Test
testStopSdkSandboxFailsForNonDebuggableApp()161     public void testStopSdkSandboxFailsForNonDebuggableApp() throws Exception {
162         startActivity(APP_PACKAGE, APP_ACTIVITY);
163         waitForProcessStart(APP_SANDBOX_NAME);
164 
165         CommandResult output = getDevice().executeShellV2Command(
166                 String.format("cmd sdk_sandbox stop %s", APP_PACKAGE));
167         assertThat(output.getStatus()).isEqualTo(CommandStatus.FAILED);
168 
169         String processDump = getDevice().executeShellCommand("ps -A");
170         assertThat(processDump).contains(APP_SANDBOX_NAME);
171     }
172 
173     @Test
testStopSdkSandboxFailsForIncorrectUser()174     public void testStopSdkSandboxFailsForIncorrectUser() throws Exception {
175         startActivity(DEBUGGABLE_APP_PACKAGE, DEBUGGABLE_APP_ACTIVITY);
176         waitForProcessStart(DEBUGGABLE_APP_SANDBOX_NAME);
177 
178         int otherUserId = getDevice().createUser("TestUser_" + System.currentTimeMillis());
179         CommandResult output = getDevice().executeShellV2Command(String.format(
180                 "cmd sdk_sandbox stop --user %s %s", otherUserId, DEBUGGABLE_APP_PACKAGE));
181         assertThat(output.getStatus()).isEqualTo(CommandStatus.FAILED);
182 
183         String processDump = getDevice().executeShellCommand("ps -A");
184         assertThat(processDump).contains(DEBUGGABLE_APP_SANDBOX_NAME);
185     }
186 
clearProcess(String pkg)187     private void clearProcess(String pkg) throws Exception {
188         getDevice().executeShellCommand(String.format("pm clear %s", pkg));
189     }
190 
startActivity(String pkg, String activity)191     private void startActivity(String pkg, String activity) throws Exception {
192         getDevice().executeShellCommand(String.format("am start -W -n %s/.%s", pkg, activity));
193     }
194 
waitForProcessStart(String processName)195     private void waitForProcessStart(String processName) throws Exception {
196         AwaitUtils.waitFor(
197                 () -> {
198                     String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
199                     return processDump.contains(processName);
200                 },
201                 "Process " + processName + " has not started.");
202     }
203 
waitForProcessDeath(String processName)204     private void waitForProcessDeath(String processName) throws Exception {
205         AwaitUtils.waitFor(
206                 () -> {
207                     String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
208                     return !processDump.contains(processName);
209                 },
210                 "Process " + processName + " has not died.");
211     }
212 }
213