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