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.cts.appcloning;
18 
19 
20 import static org.junit.Assert.assertNotNull;
21 
22 import com.android.modules.utils.build.testing.DeviceSdkLevel;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.device.NativeDevice;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
28 import com.android.tradefed.util.CommandResult;
29 import com.android.tradefed.util.CommandStatus;
30 import com.android.tradefed.util.RunUtil;
31 
32 import com.google.common.collect.Iterables;
33 
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.function.BooleanSupplier;
38 
39 
40 abstract class BaseHostTestCase extends BaseHostJUnit4Test {
41     private int mCurrentUserId = NativeDevice.INVALID_USER_ID;
42     private static final String ERROR_MESSAGE_TAG = "[ERROR]";
43     protected static ITestDevice sDevice = null;
44     private static final String MEDIA_PROVIDER_MODULE_NAME = "android.providers.media.module";
45 
setDevice(ITestDevice device)46     protected static void setDevice(ITestDevice device) {
47         sDevice = device;
48     }
49 
executeShellCommand(String cmd, Object... args)50     protected String executeShellCommand(String cmd, Object... args) throws Exception {
51         return sDevice.executeShellCommand(String.format(cmd, args));
52     }
53 
executeShellV2Command(String cmd, Object... args)54     protected CommandResult executeShellV2Command(String cmd, Object... args) throws Exception {
55         return sDevice.executeShellV2Command(String.format(cmd, args));
56     }
57 
isPackageInstalled(String packageName, String userId)58     protected boolean isPackageInstalled(String packageName, String userId) throws Exception {
59         return sDevice.isPackageInstalled(packageName, userId);
60     }
61 
62     // TODO (b/174775905) remove after exposing the check from ITestDevice.
isHeadlessSystemUserMode()63     protected static boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
64         String result = sDevice
65                 .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
66         return "true".equalsIgnoreCase(result);
67     }
68 
supportsMultipleUsers()69     protected static boolean supportsMultipleUsers() throws DeviceNotAvailableException {
70         return sDevice.getMaxNumberOfUsersSupported() > 1;
71     }
72 
isAtLeastS()73     protected static boolean isAtLeastS() throws DeviceNotAvailableException {
74         DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(sDevice);
75         return deviceSdkLevel.isDeviceAtLeastS();
76     }
77 
isAtLeastT()78     protected boolean isAtLeastT() throws DeviceNotAvailableException {
79         DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(sDevice);
80         return deviceSdkLevel.isDeviceAtLeastT();
81     }
82 
isAtLeastU(ITestDevice device)83     protected static boolean isAtLeastU(ITestDevice device) throws DeviceNotAvailableException {
84         DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(device);
85         return deviceSdkLevel.isDeviceAtLeastU();
86     }
87 
isAtLeastV(ITestDevice device)88     protected static boolean isAtLeastV(ITestDevice device) throws DeviceNotAvailableException {
89         DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(device);
90         return deviceSdkLevel.isDeviceAtLeastV();
91     }
92 
throwExceptionIfTimeout(long start, long timeoutMillis, Throwable e)93     protected static void throwExceptionIfTimeout(long start, long timeoutMillis, Throwable e) {
94         if (System.currentTimeMillis() - start < timeoutMillis) {
95             RunUtil.getDefault().sleep(100);
96         } else {
97             throw new RuntimeException(e);
98         }
99     }
100 
eventually(ThrowingRunnable r, long timeoutMillis)101     protected static void eventually(ThrowingRunnable r, long timeoutMillis) {
102         long start = System.currentTimeMillis();
103 
104         while (true) {
105             try {
106                 r.run();
107                 return;
108             } catch (Throwable e) {
109                 throwExceptionIfTimeout(start, timeoutMillis, e);
110             }
111         }
112     }
113 
eventually(ThrowingBooleanSupplier booleanSupplier, long timeoutMillis, String failureMessage)114     protected static void eventually(ThrowingBooleanSupplier booleanSupplier,
115             long timeoutMillis, String failureMessage) {
116         long start = System.currentTimeMillis();
117 
118         while (true) {
119             try {
120                 if (booleanSupplier.getAsBoolean()) {
121                     return;
122                 }
123 
124                 throw new RuntimeException(failureMessage);
125             } catch (Throwable e) {
126                 throwExceptionIfTimeout(start, timeoutMillis, e);
127             }
128         }
129     }
130 
getCurrentUserId()131     protected int getCurrentUserId() throws Exception {
132         setCurrentUserId();
133 
134         return mCurrentUserId;
135     }
136 
isSuccessful(CommandResult result)137     protected static boolean isSuccessful(CommandResult result) {
138         if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
139             return false;
140         }
141         String stdout = result.getStdout();
142         if (stdout.contains(ERROR_MESSAGE_TAG)) {
143             return false;
144         }
145         String stderr = result.getStderr();
146         return (stderr == null || stderr.trim().isEmpty());
147     }
148 
setCurrentUserId()149     private void setCurrentUserId() throws Exception {
150         if (mCurrentUserId != NativeDevice.INVALID_USER_ID) return;
151 
152         mCurrentUserId = sDevice.getCurrentUser();
153         CLog.i("Current user: %d");
154     }
155 
156     protected interface ThrowingRunnable {
157         /**
158          * Similar to {@link Runnable#run} but has {@code throws Exception}.
159          */
run()160         void run() throws Exception;
161     }
162 
163     protected interface ThrowingBooleanSupplier {
164         /**
165          * Similar to {@link BooleanSupplier#getAsBoolean} but has {@code throws Exception}.
166          */
getAsBoolean()167         boolean getAsBoolean() throws Exception;
168     }
169 
getPublicVolumeExcluding(String excludingVolume)170     protected static String getPublicVolumeExcluding(String excludingVolume) throws Exception {
171         List<String> volList = splitMultiLineOutput(sDevice.executeShellCommand("sm list-volumes"));
172 
173         // list volumes will result in something like
174         // private mounted null
175         // public:7,281 mounted 3080-17E8
176         // emulated;0 mounted null
177         // and we are interested in 3080-17E8
178         for (String volume: volList) {
179             if (volume.contains("public")
180                     && (excludingVolume == null || !volume.contains(excludingVolume))) {
181                 //public:7,281 mounted 3080-17E8
182                 String[] splits = volume.split(" ");
183                 //Return the last snippet, that is 3080-17E8
184                 return splits[splits.length - 1];
185             }
186         }
187         return null;
188     }
189 
partitionDisks()190     protected static boolean partitionDisks() {
191         try {
192             List<String> diskNames = splitMultiLineOutput(sDevice
193                     .executeShellCommand("sm list-disks"));
194             if (!diskNames.isEmpty() && !Iterables.getLast(diskNames).isEmpty()) {
195                 sDevice.executeShellCommand("sm partition "
196                         + Iterables.getLast(diskNames) + " public");
197                 return true;
198             }
199         } catch (Exception ignored) {
200             //ignored
201         }
202         return false;
203     }
204 
getMediaProviderProcess(String userId)205     protected String getMediaProviderProcess(String userId) throws Exception {
206         String processUserPrefix = "u" + userId;
207         List<String> mediaProcessList = splitMultiLineOutput(sDevice
208                 .executeShellCommand("ps -A -o user,name,pid | grep -i "
209                         + MEDIA_PROVIDER_MODULE_NAME));
210         // Media Process list will be something like:
211         // u0_a246      com.google.android.providers.media.module 21403
212         // u10_a246     com.google.android.providers.media.module 25576
213         for (String mediaProcess : mediaProcessList) {
214             if (mediaProcess.startsWith(processUserPrefix)) {
215                 //u0_a246      com.google.android.providers.media.module 21403
216                 String[] splits = mediaProcess.split(" ");
217                 //Return the last snippet, that is 21403
218                 return splits[splits.length - 1];
219             }
220         }
221         return null;
222     }
223 
isUserVolumeMounted(String userId)224     protected boolean isUserVolumeMounted(String userId) throws Exception {
225         String mountStatus = sDevice
226                 .executeShellCommand("dumpsys mount | grep mountUserId=" + userId);
227         // Mount Status will be something like:
228         // type=EMULATED diskId=null partGuid= mountFlags=PRIMARY|VISIBLE_FOR_WRITE mountUserId=11
229         // state=MOUNTED
230         assertNotNull("No Volumes Found for user " + userId, mountStatus);
231         return mountStatus.contains("state=MOUNTED");
232     }
233 
splitMultiLineOutput(String input)234     private static List<String> splitMultiLineOutput(String input) throws Exception {
235         if (input == null) {
236             return new ArrayList<>();
237         }
238         return Arrays.asList(input.split("\\r?\\n"));
239     }
240 }
241