• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 android.content.pm;
18 
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.AndroidDebugBridge;
21 import com.android.ddmlib.IDevice;
22 import com.android.ddmlib.IShellOutputReceiver;
23 import com.android.ddmlib.InstallException;
24 import com.android.ddmlib.Log;
25 import com.android.ddmlib.MultiLineReceiver;
26 import com.android.ddmlib.ShellCommandUnresponsiveException;
27 import com.android.ddmlib.SyncException;
28 import com.android.ddmlib.TimeoutException;
29 import com.android.ddmlib.SyncService.ISyncProgressMonitor;
30 import com.android.ddmlib.testrunner.ITestRunListener;
31 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
32 import com.android.ddmlib.testrunner.TestIdentifier;
33 
34 import java.io.BufferedReader;
35 import java.io.IOException;
36 import java.io.InputStreamReader;
37 import java.io.StringReader;
38 import java.lang.Runtime;
39 import java.lang.Process;
40 import java.util.Hashtable;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45 
46 import junit.framework.Assert;
47 
48 /**
49  * Set of tests that verify host side install cases
50  */
51 public class PackageManagerHostTestUtils extends Assert {
52 
53     private static final String LOG_TAG = "PackageManagerHostTests";
54     private IDevice mDevice = null;
55 
56     // TODO: get this value from Android Environment instead of hardcoding
57     private static final String APP_PRIVATE_PATH = "/data/app-private/";
58     private static final String DEVICE_APP_PATH = "/data/app/";
59     private static final String SDCARD_APP_PATH = "/mnt/secure/asec/";
60 
61     private static final int MAX_WAIT_FOR_DEVICE_TIME = 120 * 1000;
62     private static final int WAIT_FOR_DEVICE_POLL_TIME = 10 * 1000;
63     private static final int MAX_WAIT_FOR_APP_LAUNCH_TIME = 60 * 1000;
64     private static final int WAIT_FOR_APP_LAUNCH_POLL_TIME = 5 * 1000;
65 
66     // Install preference on the device-side
67     public static enum InstallLocPreference {
68         AUTO,
69         INTERNAL,
70         EXTERNAL
71     }
72 
73     // Actual install location
74     public static enum InstallLocation {
75         DEVICE,
76         SDCARD
77     }
78 
79     /**
80      * Constructor takes the device to use
81      * @param the device to use when performing operations
82      */
PackageManagerHostTestUtils(IDevice device)83     public PackageManagerHostTestUtils(IDevice device)
84     {
85           mDevice = device;
86     }
87 
88     /**
89      * Disable default constructor
90      */
PackageManagerHostTestUtils()91     private PackageManagerHostTestUtils() {}
92 
93     /**
94      * Returns the path on the device of forward-locked apps.
95      *
96      * @return path of forward-locked apps on the device
97      */
getAppPrivatePath()98     public static String getAppPrivatePath() {
99         return APP_PRIVATE_PATH;
100     }
101 
102     /**
103      * Returns the path on the device of normal apps.
104      *
105      * @return path of forward-locked apps on the device
106      */
getDeviceAppPath()107     public static String getDeviceAppPath() {
108         return DEVICE_APP_PATH;
109     }
110 
111     /**
112      * Returns the path of apps installed on the SD card.
113      *
114      * @return path of forward-locked apps on the device
115      */
getSDCardAppPath()116     public static String getSDCardAppPath() {
117         return SDCARD_APP_PATH;
118     }
119 
120     /**
121      * Helper method to run tests and return the listener that collected the results.
122      *
123      * For the optional params, pass null to use the default values.
124 
125      * @param pkgName Android application package for tests
126      * @param className (optional) The class containing the method to test
127      * @param methodName (optional) The method in the class of which to test
128      * @param runnerName (optional) The name of the TestRunner of the test on the device to be run
129      * @param params (optional) Any additional parameters to pass into the Test Runner
130      * @throws TimeoutException in case of a timeout on the connection.
131      * @throws AdbCommandRejectedException if adb rejects the command
132      * @throws ShellCommandUnresponsiveException if the device did not output anything for
133      * a period longer than the max time to output.
134      * @throws IOException if connection to device was lost.
135      * @return the {@link CollectingTestRunListener}
136      */
doRunTests(String pkgName, String className, String methodName, String runnerName, Map<String, String> params)137     private CollectingTestRunListener doRunTests(String pkgName, String className,
138             String methodName, String runnerName, Map<String, String> params) throws IOException,
139             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
140         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, runnerName,
141                 mDevice);
142 
143         if (className != null && methodName != null) {
144             testRunner.setMethodName(className, methodName);
145         }
146 
147         // Add in any additional args to pass into the test
148         if (params != null) {
149             for (Entry<String, String> argPair : params.entrySet()) {
150                 testRunner.addInstrumentationArg(argPair.getKey(), argPair.getValue());
151             }
152         }
153 
154         CollectingTestRunListener listener = new CollectingTestRunListener();
155         try {
156             testRunner.run(listener);
157         } catch (IOException ioe) {
158             Log.w(LOG_TAG, "encountered IOException " + ioe);
159         }
160         return listener;
161     }
162 
163     /**
164      * Runs the specified packages tests, and returns whether all tests passed or not.
165      *
166      * @param pkgName Android application package for tests
167      * @param className The class containing the method to test
168      * @param methodName The method in the class of which to test
169      * @param runnerName The name of the TestRunner of the test on the device to be run
170      * @param params Any additional parameters to pass into the Test Runner
171      * @return true if test passed, false otherwise.
172      */
runDeviceTestsDidAllTestsPass(String pkgName, String className, String methodName, String runnerName, Map<String, String> params)173     public boolean runDeviceTestsDidAllTestsPass(String pkgName, String className,
174             String methodName, String runnerName, Map<String, String> params) throws IOException,
175             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
176         CollectingTestRunListener listener = doRunTests(pkgName, className, methodName,
177                 runnerName, params);
178         return listener.didAllTestsPass();
179     }
180 
181     /**
182      * Runs the specified packages tests, and returns whether all tests passed or not.
183      *
184      * @param pkgName Android application package for tests
185      * @throws TimeoutException in case of a timeout on the connection.
186      * @throws AdbCommandRejectedException if adb rejects the command
187      * @throws ShellCommandUnresponsiveException if the device did not output anything for
188      * a period longer than the max time to output.
189      * @throws IOException if connection to device was lost.
190      * @return true if every test passed, false otherwise.
191      */
runDeviceTestsDidAllTestsPass(String pkgName)192     public boolean runDeviceTestsDidAllTestsPass(String pkgName) throws IOException,
193             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
194         CollectingTestRunListener listener = doRunTests(pkgName, null, null, null, null);
195         return listener.didAllTestsPass();
196     }
197 
198     /**
199      * Helper method to push a file to device
200      * @param apkAppPrivatePath
201      * @throws TimeoutException in case of a timeout on the connection.
202      * @throws AdbCommandRejectedException if adb rejects the command
203      * @throws IOException if connection to device was lost.
204      * @throws SyncException if the sync failed for another reason.
205      */
pushFile(final String localFilePath, final String destFilePath)206     public void pushFile(final String localFilePath, final String destFilePath)
207             throws IOException, SyncException, TimeoutException, AdbCommandRejectedException {
208         mDevice.getSyncService().pushFile(localFilePath,
209                 destFilePath, new NullSyncProgressMonitor());
210     }
211 
212     /**
213      * Helper method to install a file
214      * @param localFilePath the absolute file system path to file on local host to install
215      * @param reinstall set to <code>true</code> if re-install of app should be performed
216      * @throws IOException if connection to device was lost.
217      * @throws InstallException if the install failed
218      */
installFile(final String localFilePath, final boolean replace)219     public void installFile(final String localFilePath, final boolean replace) throws IOException,
220             InstallException {
221         String result = mDevice.installPackage(localFilePath, replace);
222         assertEquals(null, result);
223     }
224 
225     /**
226      * Helper method to install a file that should not be install-able
227      * @param localFilePath the absolute file system path to file on local host to install
228      * @param reinstall set to <code>true</code> if re-install of app should be performed
229      * @return the string output of the failed install attempt
230      * @throws IOException if connection to device was lost.
231      * @throws InstallException if the install failed
232      */
installFileFail(final String localFilePath, final boolean replace)233     public String installFileFail(final String localFilePath, final boolean replace)
234             throws IOException, InstallException {
235         String result = mDevice.installPackage(localFilePath, replace);
236         assertNotNull(result);
237         return result;
238     }
239 
240     /**
241      * Helper method to install a file to device as forward locked
242      * @param localFilePath the absolute file system path to file on local host to install
243      * @param reinstall set to <code>true</code> if re-install of app should be performed
244      * @throws TimeoutException in case of a timeout on the connection.
245      * @throws AdbCommandRejectedException if adb rejects the command
246      * @throws ShellCommandUnresponsiveException if the device did not output anything for
247      * a period longer than the max time to output.
248      * @throws IOException if connection to device was lost.
249      * @throws SyncException if the sync failed for another reason.
250      * @throws InstallException if the install failed.
251      */
installFileForwardLocked(final String localFilePath, final boolean replace)252     public String installFileForwardLocked(final String localFilePath, final boolean replace)
253             throws IOException, SyncException, TimeoutException, AdbCommandRejectedException,
254             ShellCommandUnresponsiveException, InstallException {
255         String remoteFilePath = mDevice.syncPackageToDevice(localFilePath);
256         InstallReceiver receiver = new InstallReceiver();
257         String cmd = String.format(replace ? "pm install -r -l \"%1$s\"" :
258                 "pm install -l \"%1$s\"", remoteFilePath);
259         mDevice.executeShellCommand(cmd, receiver);
260         mDevice.removeRemotePackage(remoteFilePath);
261         return receiver.getErrorMessage();
262     }
263 
264     /**
265      * Helper method to determine if file on device exists.
266      *
267      * @param destPath the absolute path of file on device to check
268      * @return <code>true</code> if file exists, <code>false</code> otherwise.
269      * @throws TimeoutException in case of a timeout on the connection.
270      * @throws AdbCommandRejectedException if adb rejects the command
271      * @throws ShellCommandUnresponsiveException if the device did not output anything for
272      * a period longer than the max time to output.
273      * @throws IOException if connection to device was lost.
274      */
doesRemoteFileExist(String destPath)275     public boolean doesRemoteFileExist(String destPath) throws IOException, TimeoutException,
276             AdbCommandRejectedException, ShellCommandUnresponsiveException {
277         String lsGrep = executeShellCommand(String.format("ls %s", destPath));
278         return !lsGrep.contains("No such file or directory");
279     }
280 
281     /**
282      * Helper method to determine if file exists on the device containing a given string.
283      *
284      * @param destPath the absolute path of the file
285      * @return <code>true</code> if file exists containing given string,
286      *         <code>false</code> otherwise.
287      * @throws TimeoutException in case of a timeout on the connection.
288      * @throws AdbCommandRejectedException if adb rejects the command
289      * @throws ShellCommandUnresponsiveException if the device did not output anything for
290      * a period longer than the max time to output.
291      * @throws IOException if connection to device was lost.
292      */
doesRemoteFileExistContainingString(String destPath, String searchString)293     public boolean doesRemoteFileExistContainingString(String destPath, String searchString)
294             throws IOException, TimeoutException, AdbCommandRejectedException,
295             ShellCommandUnresponsiveException {
296         String lsResult = executeShellCommand(String.format("ls %s", destPath));
297         return lsResult.contains(searchString);
298     }
299 
300     /**
301      * Helper method to determine if package on device exists.
302      *
303      * @param packageName the Android manifest package to check.
304      * @return <code>true</code> if package exists, <code>false</code> otherwise
305      * @throws TimeoutException in case of a timeout on the connection.
306      * @throws AdbCommandRejectedException if adb rejects the command
307      * @throws ShellCommandUnresponsiveException if the device did not output anything for
308      * a period longer than the max time to output.
309      * @throws IOException if connection to device was lost.
310      */
doesPackageExist(String packageName)311     public boolean doesPackageExist(String packageName) throws IOException, TimeoutException,
312             AdbCommandRejectedException, ShellCommandUnresponsiveException {
313         String pkgGrep = executeShellCommand(String.format("pm path %s", packageName));
314         return pkgGrep.contains("package:");
315     }
316 
317     /**
318      * Determines if app was installed on device.
319      *
320      * @param packageName package name to check for
321      * @return <code>true</code> if file exists, <code>false</code> otherwise.
322      * @throws TimeoutException in case of a timeout on the connection.
323      * @throws AdbCommandRejectedException if adb rejects the command
324      * @throws ShellCommandUnresponsiveException if the device did not output anything for
325      * a period longer than the max time to output.
326      * @throws IOException if connection to device was lost.
327      */
doesAppExistOnDevice(String packageName)328     public boolean doesAppExistOnDevice(String packageName) throws IOException, TimeoutException,
329             AdbCommandRejectedException, ShellCommandUnresponsiveException {
330         return doesRemoteFileExistContainingString(DEVICE_APP_PATH, packageName);
331     }
332 
333     /**
334      * Determines if app was installed on SD card.
335      *
336      * @param packageName package name to check for
337      * @return <code>true</code> if file exists, <code>false</code> otherwise.
338      * @throws TimeoutException in case of a timeout on the connection.
339      * @throws AdbCommandRejectedException if adb rejects the command
340      * @throws ShellCommandUnresponsiveException if the device did not output anything for
341      * a period longer than the max time to output.
342      * @throws IOException if connection to device was lost.
343      */
doesAppExistOnSDCard(String packageName)344     public boolean doesAppExistOnSDCard(String packageName) throws IOException, TimeoutException,
345             AdbCommandRejectedException, ShellCommandUnresponsiveException {
346         return doesRemoteFileExistContainingString(SDCARD_APP_PATH, packageName);
347     }
348 
349     /**
350      * Helper method to determine if app was installed on SD card.
351      *
352      * @param packageName package name to check for
353      * @return <code>true</code> if file exists, <code>false</code> otherwise.
354      * @throws TimeoutException in case of a timeout on the connection.
355      * @throws AdbCommandRejectedException if adb rejects the command
356      * @throws ShellCommandUnresponsiveException if the device did not output anything for
357      * a period longer than the max time to output.
358      * @throws IOException if connection to device was lost.
359      */
doesAppExistAsForwardLocked(String packageName)360     public boolean doesAppExistAsForwardLocked(String packageName) throws IOException,
361             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
362         return doesRemoteFileExistContainingString(APP_PRIVATE_PATH, packageName);
363     }
364 
365     /**
366      * Waits for device's package manager to respond.
367      *
368      * @throws InterruptedException
369      * @throws TimeoutException in case of a timeout on the connection.
370      * @throws AdbCommandRejectedException if adb rejects the command
371      * @throws ShellCommandUnresponsiveException if the device did not output anything for
372      * a period longer than the max time to output.
373      * @throws IOException if connection to device was lost.
374      */
waitForPackageManager()375     public void waitForPackageManager() throws InterruptedException, IOException, TimeoutException,
376             AdbCommandRejectedException, ShellCommandUnresponsiveException {
377         Log.i(LOG_TAG, "waiting for device");
378         int currentWaitTime = 0;
379         // poll the package manager until it returns something for android
380         while (!doesPackageExist("android")) {
381             Thread.sleep(WAIT_FOR_DEVICE_POLL_TIME);
382             currentWaitTime += WAIT_FOR_DEVICE_POLL_TIME;
383             if (currentWaitTime > MAX_WAIT_FOR_DEVICE_TIME) {
384                 Log.e(LOG_TAG, "time out waiting for device");
385                 throw new InterruptedException();
386             }
387         }
388     }
389 
390     /**
391      * Helper to determine if the device is currently online and visible via ADB.
392      *
393      * @return true iff the device is currently available to ADB and online, false otherwise.
394      */
deviceIsOnline()395     private boolean deviceIsOnline() {
396         AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
397         IDevice[] devices = bridge.getDevices();
398 
399         for (IDevice device : devices) {
400             // only online if the device appears in the devices list, and its state is online
401             if ((mDevice != null) &&
402                     mDevice.getSerialNumber().equals(device.getSerialNumber()) &&
403                     device.isOnline()) {
404                 return true;
405             }
406         }
407         return false;
408     }
409 
410     /**
411      * Waits for device to be online (visible to ADB) before returning, or times out if we've
412      * waited too long. Note that this only means the device is visible via ADB, not that
413      * PackageManager is fully up and running yet.
414      *
415      * @throws InterruptedException
416      * @throws IOException
417      */
waitForDeviceToComeOnline()418     public void waitForDeviceToComeOnline() throws InterruptedException, IOException {
419         Log.i(LOG_TAG, "waiting for device to be online");
420         int currentWaitTime = 0;
421 
422         // poll ADB until we see the device is online
423         while (!deviceIsOnline()) {
424             Thread.sleep(WAIT_FOR_DEVICE_POLL_TIME);
425             currentWaitTime += WAIT_FOR_DEVICE_POLL_TIME;
426             if (currentWaitTime > MAX_WAIT_FOR_DEVICE_TIME) {
427                 Log.e(LOG_TAG, "time out waiting for device");
428                 throw new InterruptedException();
429             }
430         }
431         // Note: if we try to access the device too quickly after it is "officially" online,
432         // there are sometimes strange issues where it's actually not quite ready yet,
433         // so we pause for a bit once more before actually returning.
434         Thread.sleep(WAIT_FOR_DEVICE_POLL_TIME);
435     }
436 
437     /**
438      * Queries package manager and waits until a package is launched (or times out)
439      *
440      * @param packageName The name of the package to wait to load
441      * @throws InterruptedException
442      * @throws TimeoutException in case of a timeout on the connection.
443      * @throws AdbCommandRejectedException if adb rejects the command
444      * @throws ShellCommandUnresponsiveException if the device did not output anything for
445      * a period longer than the max time to output.
446      * @throws IOException if connection to device was lost.
447      */
waitForApp(String packageName)448     public void waitForApp(String packageName) throws InterruptedException, IOException,
449             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
450         Log.i(LOG_TAG, "waiting for app to launch");
451         int currentWaitTime = 0;
452         // poll the package manager until it returns something for the package we're looking for
453         while (!doesPackageExist(packageName)) {
454             Thread.sleep(WAIT_FOR_APP_LAUNCH_POLL_TIME);
455             currentWaitTime += WAIT_FOR_APP_LAUNCH_POLL_TIME;
456             if (currentWaitTime > MAX_WAIT_FOR_APP_LAUNCH_TIME) {
457                 Log.e(LOG_TAG, "time out waiting for app to launch: " + packageName);
458                 throw new InterruptedException();
459             }
460         }
461     }
462 
463     /**
464      * Helper method which executes a adb shell command and returns output as a {@link String}
465      * @return the output of the command
466      * @throws TimeoutException in case of a timeout on the connection.
467      * @throws AdbCommandRejectedException if adb rejects the command
468      * @throws ShellCommandUnresponsiveException if the device did not output anything for
469      * a period longer than the max time to output.
470      * @throws IOException if connection to device was lost.
471      */
executeShellCommand(String command)472     public String executeShellCommand(String command) throws IOException, TimeoutException,
473             AdbCommandRejectedException, ShellCommandUnresponsiveException {
474         Log.i(LOG_TAG, String.format("adb shell %s", command));
475         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
476         mDevice.executeShellCommand(command, receiver);
477         String output = receiver.getOutput();
478         Log.i(LOG_TAG, String.format("Result: %s", output));
479         return output;
480     }
481 
482     /**
483      * Helper method ensures we are in root mode on the host side. It returns only after
484      * PackageManager is actually up and running.
485      * @throws TimeoutException in case of a timeout on the connection.
486      * @throws AdbCommandRejectedException if adb rejects the command
487      * @throws ShellCommandUnresponsiveException if the device did not output anything for
488      * a period longer than the max time to output.
489      * @throws IOException if connection to device was lost.
490      */
runAdbRoot()491     public void runAdbRoot() throws IOException, InterruptedException, TimeoutException,
492             AdbCommandRejectedException, ShellCommandUnresponsiveException {
493         Log.i(LOG_TAG, "adb root");
494         Runtime runtime = Runtime.getRuntime();
495         Process process = runtime.exec("adb root"); // adb should be in the path
496         BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
497 
498         String nextLine = null;
499         while (null != (nextLine = output.readLine())) {
500             Log.i(LOG_TAG, nextLine);
501         }
502         process.waitFor();
503         waitForDeviceToComeOnline();
504         waitForPackageManager(); // now wait for package manager to actually load
505     }
506 
507     /**
508      * Helper method which reboots the device and returns once the device is online again
509      * and package manager is up and running (note this function is synchronous to callers).
510      * @throws InterruptedException
511      * @throws TimeoutException in case of a timeout on the connection.
512      * @throws AdbCommandRejectedException if adb rejects the command
513      * @throws ShellCommandUnresponsiveException if the device did not output anything for
514      * a period longer than the max time to output.
515      * @throws IOException if connection to device was lost.
516      */
rebootDevice()517     public void rebootDevice() throws IOException, InterruptedException, TimeoutException,
518             AdbCommandRejectedException, ShellCommandUnresponsiveException {
519         String command = "reboot"; // no need for -s since mDevice is already tied to a device
520         Log.i(LOG_TAG, command);
521         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
522         mDevice.executeShellCommand(command, receiver);
523         String output = receiver.getOutput();
524         Log.i(LOG_TAG, String.format("Result: %s", output));
525         waitForDeviceToComeOnline(); // wait for device to come online
526         runAdbRoot();
527     }
528 
529     /**
530      * A {@link IShellOutputReceiver} which collects the whole shell output into one {@link String}
531      */
532     private class CollectingOutputReceiver extends MultiLineReceiver {
533 
534         private StringBuffer mOutputBuffer = new StringBuffer();
535 
getOutput()536         public String getOutput() {
537             return mOutputBuffer.toString();
538         }
539 
540         @Override
processNewLines(String[] lines)541         public void processNewLines(String[] lines) {
542             for (String line: lines) {
543                 mOutputBuffer.append(line);
544                 mOutputBuffer.append("\n");
545             }
546         }
547 
isCancelled()548         public boolean isCancelled() {
549             return false;
550         }
551     }
552 
553     private class NullSyncProgressMonitor implements ISyncProgressMonitor {
advance(int work)554         public void advance(int work) {
555             // ignore
556         }
557 
isCanceled()558         public boolean isCanceled() {
559             // ignore
560             return false;
561         }
562 
start(int totalWork)563         public void start(int totalWork) {
564             // ignore
565 
566         }
567 
startSubTask(String name)568         public void startSubTask(String name) {
569             // ignore
570         }
571 
stop()572         public void stop() {
573             // ignore
574         }
575     }
576 
577     // For collecting results from running device tests
578     public static class CollectingTestRunListener implements ITestRunListener {
579 
580         private boolean mAllTestsPassed = true;
581         private String mTestRunErrorMessage = null;
582 
testEnded(TestIdentifier test, Map<String, String> metrics)583         public void testEnded(TestIdentifier test, Map<String, String> metrics) {
584             // ignore
585         }
586 
testFailed(TestFailure status, TestIdentifier test, String trace)587         public void testFailed(TestFailure status, TestIdentifier test,
588                 String trace) {
589             Log.w(LOG_TAG, String.format("%s#%s failed: %s", test.getClassName(),
590                     test.getTestName(), trace));
591             mAllTestsPassed = false;
592         }
593 
testRunEnded(long elapsedTime, Map<String, String> resultBundle)594         public void testRunEnded(long elapsedTime, Map<String, String> resultBundle) {
595             // ignore
596         }
597 
testRunFailed(String errorMessage)598         public void testRunFailed(String errorMessage) {
599             Log.w(LOG_TAG, String.format("test run failed: %s", errorMessage));
600             mAllTestsPassed = false;
601             mTestRunErrorMessage = errorMessage;
602         }
603 
testRunStarted(String runName, int testCount)604         public void testRunStarted(String runName, int testCount) {
605             // ignore
606         }
607 
testRunStopped(long elapsedTime)608         public void testRunStopped(long elapsedTime) {
609             // ignore
610         }
611 
testStarted(TestIdentifier test)612         public void testStarted(TestIdentifier test) {
613             // ignore
614         }
615 
didAllTestsPass()616         boolean didAllTestsPass() {
617             return mAllTestsPassed;
618         }
619 
620         /**
621          * Get the test run failure error message.
622          * @return the test run failure error message or <code>null</code> if test run completed.
623          */
getTestRunErrorMessage()624         String getTestRunErrorMessage() {
625             return mTestRunErrorMessage;
626         }
627     }
628 
629     /**
630      * Output receiver for "pm install package.apk" command line.
631      *
632      */
633     private static final class InstallReceiver extends MultiLineReceiver {
634 
635         private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
636         private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
637 
638         private String mErrorMessage = null;
639 
InstallReceiver()640         public InstallReceiver() {
641         }
642 
643         @Override
processNewLines(String[] lines)644         public void processNewLines(String[] lines) {
645             for (String line : lines) {
646                 if (line.length() > 0) {
647                     if (line.startsWith(SUCCESS_OUTPUT)) {
648                         mErrorMessage = null;
649                     } else {
650                         Matcher m = FAILURE_PATTERN.matcher(line);
651                         if (m.matches()) {
652                             mErrorMessage = m.group(1);
653                         }
654                     }
655                 }
656             }
657         }
658 
isCancelled()659         public boolean isCancelled() {
660             return false;
661         }
662 
getErrorMessage()663         public String getErrorMessage() {
664             return mErrorMessage;
665         }
666     }
667 
668     /**
669      * Helper method for installing an app to wherever is specified in its manifest, and
670      * then verifying the app was installed onto SD Card.
671      * <p/>
672      * Assumes adb is running as root in device under test.
673      *
674      * @param the path of the apk to install
675      * @param the name of the package
676      * @param <code>true</code> if the app should be overwritten, <code>false</code> otherwise
677      * @throws InterruptedException if the thread was interrupted
678      * @throws TimeoutException in case of a timeout on the connection.
679      * @throws AdbCommandRejectedException if adb rejects the command
680      * @throws ShellCommandUnresponsiveException if the device did not output anything for
681      * a period longer than the max time to output.
682      * @throws IOException if connection to device was lost.
683      * @throws InstallException if the install failed.
684      */
installAppAndVerifyExistsOnSDCard(String apkPath, String pkgName, boolean overwrite)685     public void installAppAndVerifyExistsOnSDCard(String apkPath, String pkgName, boolean overwrite)
686             throws IOException, InterruptedException, InstallException, TimeoutException,
687             AdbCommandRejectedException, ShellCommandUnresponsiveException {
688         // Start with a clean slate if we're not overwriting
689         if (!overwrite) {
690             // cleanup test app just in case it already exists
691             mDevice.uninstallPackage(pkgName);
692             // grep for package to make sure its not installed
693             assertFalse(doesPackageExist(pkgName));
694         }
695 
696         installFile(apkPath, overwrite);
697         assertTrue(doesAppExistOnSDCard(pkgName));
698         assertFalse(doesAppExistOnDevice(pkgName));
699         waitForPackageManager();
700 
701         // grep for package to make sure it is installed
702         assertTrue(doesPackageExist(pkgName));
703     }
704 
705     /**
706      * Helper method for installing an app to wherever is specified in its manifest, and
707      * then verifying the app was installed onto device.
708      * <p/>
709      * Assumes adb is running as root in device under test.
710      *
711      * @param the path of the apk to install
712      * @param the name of the package
713      * @param <code>true</code> if the app should be overwritten, <code>false</code> otherwise
714      * @throws InterruptedException if the thread was interrupted
715      * @throws TimeoutException in case of a timeout on the connection.
716      * @throws AdbCommandRejectedException if adb rejects the command
717      * @throws ShellCommandUnresponsiveException if the device did not output anything for
718      * a period longer than the max time to output.
719      * @throws IOException if connection to device was lost.
720      * @throws InstallException if the install failed.
721      */
installAppAndVerifyExistsOnDevice(String apkPath, String pkgName, boolean overwrite)722     public void installAppAndVerifyExistsOnDevice(String apkPath, String pkgName, boolean overwrite)
723             throws IOException, InterruptedException, InstallException, TimeoutException,
724             AdbCommandRejectedException, ShellCommandUnresponsiveException {
725         // Start with a clean slate if we're not overwriting
726         if (!overwrite) {
727             // cleanup test app just in case it already exists
728             mDevice.uninstallPackage(pkgName);
729             // grep for package to make sure its not installed
730             assertFalse(doesPackageExist(pkgName));
731         }
732 
733         installFile(apkPath, overwrite);
734         assertFalse(doesAppExistOnSDCard(pkgName));
735         assertTrue(doesAppExistOnDevice(pkgName));
736         waitForPackageManager();
737 
738         // grep for package to make sure it is installed
739         assertTrue(doesPackageExist(pkgName));
740     }
741 
742     /**
743      * Helper method for installing an app as forward-locked, and
744      * then verifying the app was installed in the proper forward-locked location.
745      * <p/>
746      * Assumes adb is running as root in device under test.
747      *
748      * @param the path of the apk to install
749      * @param the name of the package
750      * @param <code>true</code> if the app should be overwritten, <code>false</code> otherwise
751      * @throws InterruptedException if the thread was interrupted
752      * @throws IOException if connection to device was lost.
753      * @throws InstallException if the install failed.
754      * @throws TimeoutException in case of a timeout on the connection.
755      * @throws AdbCommandRejectedException if adb rejects the command
756      * @throws ShellCommandUnresponsiveException if the device did not output anything for
757      * a period longer than the max time to output.
758      */
installFwdLockedAppAndVerifyExists(String apkPath, String pkgName, boolean overwrite)759     public void installFwdLockedAppAndVerifyExists(String apkPath,
760             String pkgName, boolean overwrite) throws IOException, InterruptedException,
761             InstallException, SyncException, TimeoutException, AdbCommandRejectedException,
762             ShellCommandUnresponsiveException {
763         // Start with a clean slate if we're not overwriting
764         if (!overwrite) {
765             // cleanup test app just in case it already exists
766             mDevice.uninstallPackage(pkgName);
767             // grep for package to make sure its not installed
768             assertFalse(doesPackageExist(pkgName));
769         }
770 
771         String result = installFileForwardLocked(apkPath, overwrite);
772         assertEquals(null, result);
773         assertTrue(doesAppExistAsForwardLocked(pkgName));
774         assertFalse(doesAppExistOnSDCard(pkgName));
775         waitForPackageManager();
776 
777         // grep for package to make sure it is installed
778         assertTrue(doesPackageExist(pkgName));
779     }
780 
781     /**
782      * Helper method for uninstalling an app.
783      * <p/>
784      * Assumes adb is running as root in device under test.
785      *
786      * @param pkgName package name to uninstall
787      * @throws InterruptedException if the thread was interrupted
788      * @throws TimeoutException in case of a timeout on the connection.
789      * @throws AdbCommandRejectedException if adb rejects the command
790      * @throws ShellCommandUnresponsiveException if the device did not output anything for
791      * a period longer than the max time to output.
792      * @throws IOException if connection to device was lost.
793      * @throws InstallException if the uninstall failed.
794      */
uninstallApp(String pkgName)795     public void uninstallApp(String pkgName) throws IOException, InterruptedException,
796             InstallException, TimeoutException, AdbCommandRejectedException,
797             ShellCommandUnresponsiveException {
798         mDevice.uninstallPackage(pkgName);
799         // make sure its not installed anymore
800         assertFalse(doesPackageExist(pkgName));
801     }
802 
803     /**
804      * Helper method for clearing any installed non-system apps.
805      * Useful ensuring no non-system apps are installed, and for cleaning up stale files that
806      * may be lingering on the system for whatever reason.
807      * <p/>
808      * Assumes adb is running as root in device under test.
809      *
810      * @throws TimeoutException in case of a timeout on the connection.
811      * @throws AdbCommandRejectedException if adb rejects the command
812      * @throws ShellCommandUnresponsiveException if the device did not output anything for
813      * a period longer than the max time to output.
814      * @throws IOException if connection to device was lost.
815      * @throws InstallException if the uninstall failed.
816      */
wipeNonSystemApps()817     public void wipeNonSystemApps() throws IOException, TimeoutException,
818             AdbCommandRejectedException, ShellCommandUnresponsiveException, InstallException {
819       String allInstalledPackages = executeShellCommand("pm list packages -f");
820       BufferedReader outputReader = new BufferedReader(new StringReader(allInstalledPackages));
821 
822       // First use Package Manager to uninstall all non-system apps
823       String currentLine = null;
824       while ((currentLine = outputReader.readLine()) != null) {
825           // Skip over any system apps...
826           if (currentLine.contains("/system/")) {
827               continue;
828           }
829           String packageName = currentLine.substring(currentLine.indexOf('=') + 1);
830           mDevice.uninstallPackage(packageName);
831       }
832       // Make sure there are no stale app files under these directories
833       executeShellCommand(String.format("rm %s*", SDCARD_APP_PATH, "*"));
834       executeShellCommand(String.format("rm %s*", DEVICE_APP_PATH, "*"));
835       executeShellCommand(String.format("rm %s*", APP_PRIVATE_PATH, "*"));
836     }
837 
838     /**
839      * Sets the device's install location preference.
840      *
841      * <p/>
842      * Assumes adb is running as root in device under test.
843      * @throws TimeoutException in case of a timeout on the connection.
844      * @throws AdbCommandRejectedException if adb rejects the command
845      * @throws ShellCommandUnresponsiveException if the device did not output anything for
846      * a period longer than the max time to output.
847      * @throws IOException if connection to device was lost.
848      */
setDevicePreferredInstallLocation(InstallLocPreference pref)849     public void setDevicePreferredInstallLocation(InstallLocPreference pref) throws IOException,
850             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
851         String command = "pm setInstallLocation %d";
852         int locValue = 0;
853         switch (pref) {
854             case INTERNAL:
855                 locValue = 1;
856                 break;
857             case EXTERNAL:
858                 locValue = 2;
859                 break;
860             default: // AUTO
861                 locValue = 0;
862                 break;
863         }
864         executeShellCommand(String.format(command, locValue));
865     }
866 
867     /**
868      * Gets the device's install location preference.
869      *
870      * <p/>
871      * Assumes adb is running as root in device under test.
872      * @throws TimeoutException in case of a timeout on the connection.
873      * @throws AdbCommandRejectedException if adb rejects the command
874      * @throws ShellCommandUnresponsiveException if the device did not output anything for
875      * a period longer than the max time to output.
876      * @throws IOException if connection to device was lost.
877      */
getDevicePreferredInstallLocation()878     public InstallLocPreference getDevicePreferredInstallLocation() throws IOException,
879             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
880         String result = executeShellCommand("pm getInstallLocation");
881         if (result.indexOf('0') != -1) {
882             return InstallLocPreference.AUTO;
883         }
884         else if (result.indexOf('1') != -1) {
885             return InstallLocPreference.INTERNAL;
886         }
887         else {
888             return InstallLocPreference.EXTERNAL;
889         }
890     }
891 }
892