• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.compatibility.common.util;
18 
19 import static org.junit.Assert.assertTrue;
20 import static org.junit.Assert.fail;
21 
22 import android.app.ActivityManager;
23 import android.app.ActivityManager.MemoryInfo;
24 import android.app.Instrumentation;
25 import android.app.UiAutomation;
26 import android.content.Context;
27 import android.os.ParcelFileDescriptor;
28 import android.os.StatFs;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.test.platform.app.InstrumentationRegistry;
33 
34 import com.android.modules.utils.build.SdkLevel;
35 
36 import java.io.FileInputStream;
37 import java.io.IOException;
38 import java.util.concurrent.Callable;
39 import java.util.concurrent.atomic.AtomicReference;
40 import java.util.function.Predicate;
41 
42 public class SystemUtil {
43     private static final String TAG = "CtsSystemUtil";
44     private static final long TIMEOUT_MILLIS = 10000;
45 
getFreeDiskSize(Context context)46     public static long getFreeDiskSize(Context context) {
47         final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath());
48         return (long)statFs.getAvailableBlocks() * statFs.getBlockSize();
49     }
50 
getFreeMemory(Context context)51     public static long getFreeMemory(Context context) {
52         final MemoryInfo info = new MemoryInfo();
53         ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
54         return info.availMem;
55     }
56 
getTotalMemory(Context context)57     public static long getTotalMemory(Context context) {
58         final MemoryInfo info = new MemoryInfo();
59         ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
60         return info.totalMem;
61     }
62 
63     /**
64      * Executes a shell command using shell user identity, and return the standard output in string
65      * <p>Note: calling this function requires API level 21 or above
66      * @param instrumentation {@link Instrumentation} instance, obtained from a test running in
67      * instrumentation framework
68      * @param cmd the command to run
69      * @return the standard output of the command
70      * @throws Exception
71      */
runShellCommand(Instrumentation instrumentation, String cmd)72     public static String runShellCommand(Instrumentation instrumentation, String cmd)
73             throws IOException {
74         return runShellCommand(instrumentation.getUiAutomation(), cmd);
75     }
76 
77     /**
78      * Executes a shell command using shell user identity, and return the standard output in string
79      * <p>Note: calling this function requires API level 21 or above
80      * @param automation {@link UiAutomation} instance, obtained from a test running in
81      * instrumentation framework
82      * @param cmd the command to run
83      * @return the standard output of the command
84      * @throws Exception
85      */
runShellCommand(UiAutomation automation, String cmd)86     public static String runShellCommand(UiAutomation automation, String cmd)
87             throws IOException {
88         return new String(runShellCommandByteOutput(automation, cmd));
89     }
90 
91     /**
92      * Executes a shell command using shell user identity, and return the standard output as a byte
93      * array
94      * <p>Note: calling this function requires API level 21 or above
95      *
96      * @param automation {@link UiAutomation} instance, obtained from a test running in
97      *                   instrumentation framework
98      * @param cmd        the command to run
99      * @return the standard output of the command as a byte array
100      */
runShellCommandByteOutput(UiAutomation automation, String cmd)101     public static byte[] runShellCommandByteOutput(UiAutomation automation, String cmd)
102             throws IOException {
103         checkCommandBeforeRunning(cmd);
104         ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
105         try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
106             return FileUtils.readInputStreamFully(fis);
107         }
108     }
109 
checkCommandBeforeRunning(String cmd)110     private static void checkCommandBeforeRunning(String cmd) {
111         Log.v(TAG, "Running command: " + cmd);
112         if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
113             throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
114                     + "or revokeRuntimePermission() directly, which are more robust.");
115         }
116     }
117 
118     /**
119      * Simpler version of {@link #runShellCommand(Instrumentation, String)}.
120      */
runShellCommand(String cmd)121     public static String runShellCommand(String cmd) {
122         try {
123             return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
124         } catch (IOException e) {
125             fail("Failed reading command output: " + e);
126             return "";
127         }
128     }
129 
130     /**
131      * Like {@link #runShellCommand(String)} but throws if anything was printed to stderr on S+, and
132      * delegates to {@link #runShellCommand(String)} on older platforms for compatibility.
133      */
runShellCommandOrThrow(String cmd)134     public static String runShellCommandOrThrow(String cmd) {
135         if (!SdkLevel.isAtLeastS()) {
136             return runShellCommand(cmd);
137         }
138         UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
139         try {
140             checkCommandBeforeRunning(cmd);
141 
142             ParcelFileDescriptor[] fds = automation.executeShellCommandRwe(cmd);
143             ParcelFileDescriptor fdOut = fds[0];
144             ParcelFileDescriptor fdIn = fds[1];
145             ParcelFileDescriptor fdErr = fds[2];
146 
147             if (fdIn != null) {
148                 try {
149                     // not using stdin
150                     fdIn.close();
151                 } catch (Exception e) {
152                     // Ignore
153                 }
154             }
155 
156             String out;
157             String err;
158             try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
159                 out = new String(FileUtils.readInputStreamFully(fis));
160             }
161             try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdErr)) {
162                 err = new String(FileUtils.readInputStreamFully(fis));
163             }
164             if (!err.isEmpty()) {
165                 fail("Command failed:\n$ " + cmd +
166                         "\n\nstderr:\n" + err +
167                         "\n\nstdout:\n" + out);
168             }
169             return out;
170         } catch (IOException e) {
171             fail("Failed reading command output: " + e);
172             return "";
173         }
174     }
175 
176     /**
177      * Same as {@link #runShellCommand(String)}, with optionally
178      * check the result using {@code resultChecker}.
179      */
runShellCommand(String cmd, Predicate<String> resultChecker)180     public static String runShellCommand(String cmd, Predicate<String> resultChecker) {
181         final String result = runShellCommand(cmd);
182         if (resultChecker != null) {
183             assertTrue("Assertion failed. Command was: " + cmd + "\n"
184                     + "Output was:\n" + result,
185                     resultChecker.test(result));
186         }
187         return result;
188     }
189 
190     /**
191      * Same as {@link #runShellCommand(String)}, but fails if the output is not empty.
192      */
runShellCommandForNoOutput(String cmd)193     public static String runShellCommandForNoOutput(String cmd) {
194         final String result = runShellCommand(cmd);
195         assertTrue("Command failed. Command was: " + cmd + "\n"
196                 + "Didn't expect any output, but the output was:\n" + result,
197                 result.length() == 0);
198         return result;
199     }
200 
201     /**
202      * Runs a command and print the result on logcat.
203      */
runCommandAndPrintOnLogcat(String logtag, String cmd)204     public static void runCommandAndPrintOnLogcat(String logtag, String cmd) {
205         Log.i(logtag, "Executing: " + cmd);
206         final String output = runShellCommand(cmd);
207         for (String line : output.split("\\n", -1)) {
208             Log.i(logtag, line);
209         }
210     }
211 
212     /**
213      * Runs a command and return the section matching the patterns.
214      *
215      * @see TextUtils#extractSection
216      */
runCommandAndExtractSection(String cmd, String extractionStartRegex, boolean startInclusive, String extractionEndRegex, boolean endInclusive)217     public static String runCommandAndExtractSection(String cmd,
218             String extractionStartRegex, boolean startInclusive,
219             String extractionEndRegex, boolean endInclusive) {
220         return TextUtils.extractSection(runShellCommand(cmd), extractionStartRegex, startInclusive,
221                 extractionEndRegex, endInclusive);
222     }
223 
224     /**
225      * Runs a {@link ThrowingSupplier} adopting Shell's permissions, and returning the result.
226      */
runWithShellPermissionIdentity(@onNull ThrowingSupplier<T> supplier)227     public static <T> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier) {
228         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
229         AtomicReference<T> result = new AtomicReference<>();
230         runWithShellPermissionIdentity(automan, () -> result.set(supplier.get()));
231         return result.get();
232     }
233 
234     /**
235      * Runs a {@link ThrowingSupplier} adopting a subset of Shell's permissions,
236      * and returning the result.
237      */
runWithShellPermissionIdentity(@onNull ThrowingSupplier<T> supplier, String... permissions)238     public static <T> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier,
239             String... permissions) {
240         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
241         AtomicReference<T> result = new AtomicReference<>();
242         runWithShellPermissionIdentity(automan, () -> result.set(supplier.get()), permissions);
243         return result.get();
244     }
245 
246     /**
247      * Runs a {@link ThrowingRunnable} adopting Shell's permissions.
248      */
runWithShellPermissionIdentity(@onNull ThrowingRunnable runnable)249     public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
250         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
251         runWithShellPermissionIdentity(automan, runnable);
252     }
253 
254     /**
255      * Runs a {@link ThrowingRunnable} adopting a subset of Shell's permissions.
256      */
runWithShellPermissionIdentity(@onNull ThrowingRunnable runnable, String... permissions)257     public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable,
258             String... permissions) {
259         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
260         runWithShellPermissionIdentity(automan, runnable, permissions);
261     }
262 
263     /**
264      * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the
265      * uiAutomation used.
266      */
runWithShellPermissionIdentity( @onNull UiAutomation automan, @NonNull ThrowingRunnable runnable)267     public static void runWithShellPermissionIdentity(
268             @NonNull UiAutomation automan, @NonNull ThrowingRunnable runnable) {
269         runWithShellPermissionIdentity(automan, runnable, null /* permissions */);
270     }
271 
272     /**
273      * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the
274      * uiAutomation used.
275      * @param automan UIAutomation to use.
276      * @param runnable The code to run with Shell's identity.
277      * @param permissions A subset of Shell's permissions. Passing {@code null} will use all
278      *                    available permissions.
279      */
runWithShellPermissionIdentity(@onNull UiAutomation automan, @NonNull ThrowingRunnable runnable, String... permissions)280     public static void runWithShellPermissionIdentity(@NonNull UiAutomation automan,
281             @NonNull ThrowingRunnable runnable, String... permissions) {
282         automan.adoptShellPermissionIdentity(permissions);
283         try {
284             runnable.run();
285         } catch (Exception e) {
286             throw new RuntimeException("Caught exception", e);
287         } finally {
288             automan.dropShellPermissionIdentity();
289         }
290     }
291 
292     /**
293      * Calls a {@link Callable} adopting Shell's permissions.
294      */
callWithShellPermissionIdentity(@onNull Callable<T> callable)295     public static <T> T callWithShellPermissionIdentity(@NonNull Callable<T> callable)
296             throws Exception {
297         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
298         automan.adoptShellPermissionIdentity();
299         try {
300             return callable.call();
301         } finally {
302             automan.dropShellPermissionIdentity();
303         }
304     }
305 
306     /**
307      * Calls a {@link Callable} adopting Shell's permissions.
308      *
309      * @param callable The code to call with Shell's identity.
310      * @param permissions A subset of Shell's permissions. Passing {@code null} will use all
311      *                    available permissions.     */
callWithShellPermissionIdentity(@onNull Callable<T> callable, String... permissions)312     public static <T> T callWithShellPermissionIdentity(@NonNull Callable<T> callable,
313             String... permissions) throws Exception {
314         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
315         automan.adoptShellPermissionIdentity(permissions);
316         try {
317             return callable.call();
318         } finally {
319             automan.dropShellPermissionIdentity();
320         }
321     }
322 
323     /**
324      * Make sure that a {@link Runnable} eventually finishes without throwing a {@link
325      * Exception}.
326      *
327      * @param r The {@link Runnable} to run.
328      */
eventually(@onNull ThrowingRunnable r)329     public static void eventually(@NonNull ThrowingRunnable r) {
330         eventually(r, TIMEOUT_MILLIS);
331     }
332 
333     /**
334      * Make sure that a {@link Runnable} eventually finishes without throwing a {@link
335      * Exception}.
336      *
337      * @param r The {@link Runnable} to run.
338      * @param r The number of milliseconds to wait for r to not throw
339      */
eventually(@onNull ThrowingRunnable r, long timeoutMillis)340     public static void eventually(@NonNull ThrowingRunnable r, long timeoutMillis) {
341         long start = System.currentTimeMillis();
342 
343         while (true) {
344             try {
345                 r.run();
346                 return;
347             } catch (Throwable e) {
348                 if (System.currentTimeMillis() - start < timeoutMillis) {
349                     try {
350                         Thread.sleep(100);
351                     } catch (InterruptedException ignored) {
352                         throw new RuntimeException(e);
353                     }
354                 } else {
355                     throw new RuntimeException(e);
356                 }
357             }
358         }
359     }
360 
361     /**
362      * Make sure that a {@link Callable} eventually finishes without throwing a {@link
363      * Exception}.
364      *
365      * @param c The {@link Callable} to run.
366      *
367      * @return The return value of {@code c}
368      */
getEventually(@onNull Callable<T> c)369     public static <T> T getEventually(@NonNull Callable<T> c) throws Exception {
370         return getEventually(c, TIMEOUT_MILLIS);
371     }
372 
373     /**
374      * Make sure that a {@link Callable} eventually finishes without throwing a {@link
375      * Exception}.
376      *
377      * @param c The {@link Callable} to run.
378      * @param timeoutMillis The number of milliseconds to wait for r to not throw
379      *
380      * @return The return value of {@code c}
381      */
getEventually(@onNull Callable<T> c, long timeoutMillis)382     public static <T> T getEventually(@NonNull Callable<T> c, long timeoutMillis) throws Exception {
383         long start = System.currentTimeMillis();
384 
385         while (true) {
386             try {
387                 return c.call();
388             } catch (Throwable e) {
389                 if (System.currentTimeMillis() - start < timeoutMillis) {
390                     try {
391                         Thread.sleep(100);
392                     } catch (InterruptedException ignored) {
393                         throw new RuntimeException(e);
394                     }
395                 } else {
396                     throw e;
397                 }
398             }
399         }
400     }
401 
waitForBroadcasts()402     public static void waitForBroadcasts() {
403         String cmd;
404         if (SdkLevel.isAtLeastU()) {
405             // wait for pending broadcasts to be completed.
406             cmd = "am wait-for-broadcast-barrier";
407         } else {
408             // wait for broadcast queues to be idle.
409             cmd = "am wait-for-broadcast-idle";
410         }
411         runShellCommand(cmd);
412     }
413 }
414