1 /*
2  * Copyright (C) 2011 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.tradefed.targetprep;
18 
19 import com.android.ddmlib.Log;
20 import com.android.tradefed.build.IBuildInfo;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.Option.Importance;
23 import com.android.tradefed.config.OptionClass;
24 import com.android.tradefed.device.CollectingOutputReceiver;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.util.AbiFormatter;
29 import com.google.common.annotations.VisibleForTesting;
30 
31 import java.io.File;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.List;
35 import java.util.concurrent.TimeUnit;
36 
37 /**
38  * A {@link ITargetPreparer} that installs one or more apks located on the filesystem.
39  *
40  * <p>This class should only be used for installing apks from the filesystem when all versions of
41  * the test rely on the apk being on the filesystem. For tests which use {@link TestAppInstallSetup}
42  * to install apks from the tests zip file, use {@code --alt-dir} to specify an alternate directory
43  * on the filesystem containing the apk for other test configurations (for example, local runs where
44  * the tests zip file is not present).
45  */
46 @OptionClass(alias = "install-apk")
47 public class InstallApkSetup extends BaseTargetPreparer {
48 
49     private static final String LOG_TAG = InstallApkSetup.class.getSimpleName();
50 
51     @Option(name = "apk-path", description =
52             "the filesystem path of the apk to install. Can be repeated.",
53             importance = Importance.IF_UNSET)
54     private Collection<File> mApkPaths = new ArrayList<File>();
55 
56     @Option(name = AbiFormatter.FORCE_ABI_STRING,
57             description = AbiFormatter.FORCE_ABI_DESCRIPTION,
58             importance = Importance.IF_UNSET)
59     private String mForceAbi = null;
60 
61     @Option(name = "install-arg",
62             description = "Additional arguments to be passed to install command, "
63                     + "including leading dash, e.g. \"-d\"")
64     private Collection<String> mInstallArgs = new ArrayList<>();
65 
66     @Option(name = "force-queryable",
67             description = "Whether apks should be installed as force queryable.")
68     private boolean mForceQueryable = true;
69 
70     @Option(name = "post-install-cmd", description =
71             "optional post-install adb shell commands; can be repeated.")
72     private List<String> mPostInstallCmds = new ArrayList<>();
73 
74     @Option(name = "post-install-cmd-timeout", description =
75             "max time allowed in ms for a post-install adb shell command." +
76                     "DeviceUnresponsiveException will be thrown if it is timed out.")
77     private long mPostInstallCmdTimeout = 2 * 60 * 1000;  // default to 2 minutes
78 
79     @Option(name = "throw-if-install-fail", description =
80             "Throw exception if the APK installation failed due to any reason.")
81     private boolean mThrowIfInstallFail = false;
82 
83     /**
84      * {@inheritDoc}
85      */
86     @Override
setUp(ITestDevice device, IBuildInfo buildInfo)87     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
88             BuildError, DeviceNotAvailableException {
89         for (File apk : mApkPaths) {
90             if (!apk.exists()) {
91                 throw new TargetSetupError(String.format("%s does not exist",
92                         apk.getAbsolutePath()), device.getDeviceDescriptor());
93             }
94             Log.i(LOG_TAG, String.format("Installing %s on %s", apk.getName(),
95                     device.getSerialNumber()));
96             if (mForceAbi != null) {
97                 String abi = AbiFormatter.getDefaultAbi(device, mForceAbi);
98                 if (abi != null) {
99                     mInstallArgs.add(String.format("--abi %s", abi));
100                 }
101             }
102             if (mForceQueryable && device.isAppEnumerationSupported()
103                     && !mInstallArgs.contains("--force-queryable")) {
104                 mInstallArgs.add("--force-queryable");
105             }
106             String result = device.installPackage(apk, true, mInstallArgs.toArray(new String[]{}));
107             if (result != null) {
108                 if (mThrowIfInstallFail) {
109                     throw new TargetSetupError(String.format(
110                             "Stopping test: failed to install %s on device %s. Reason: %s",
111                             apk.getAbsolutePath(), device.getSerialNumber(), result),
112                             device.getDeviceDescriptor());
113                 }
114                 Log.e(LOG_TAG, String.format("Failed to install %s on device %s. Reason: %s",
115                         apk.getAbsolutePath(), device.getSerialNumber(), result));
116             }
117         }
118 
119         if (mPostInstallCmds != null && !mPostInstallCmds.isEmpty()) {
120             for (String cmd : mPostInstallCmds) {
121                 // If the command had any output, the executeShellCommand method will log it at the
122                 // VERBOSE level; so no need to do any logging from here.
123                 CLog.d("About to run setup command on device %s: %s",
124                         device.getSerialNumber(), cmd);
125                 device.executeShellCommand(cmd, new CollectingOutputReceiver(),
126                         mPostInstallCmdTimeout, TimeUnit.MILLISECONDS, 1);
127             }
128         }
129     }
130 
getApkPaths()131     protected Collection<File> getApkPaths() {
132         return mApkPaths;
133     }
134 
135     /**
136      * Sets APK paths. Exposed for testing.
137      */
138     @VisibleForTesting
setApkPaths(Collection<File> paths)139     public void setApkPaths(Collection<File> paths) {
140         mApkPaths = paths;
141     }
142 
143     /**
144      * Set throw if install fail. Exposed for testing.
145      */
146     @VisibleForTesting
setThrowIfInstallFail(boolean throwIfInstallFail)147     public void setThrowIfInstallFail(boolean throwIfInstallFail) {
148         mThrowIfInstallFail = throwIfInstallFail;
149     }
150 }
151