1 /* 2 * Copyright (C) 2019 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 package com.android.compatibility.common.tradefed.targetprep; 17 18 import com.android.ddmlib.Log.LogLevel; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.config.OptionClass; 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.ITestDevice; 24 import com.android.tradefed.invoker.IInvocationContext; 25 import com.android.tradefed.invoker.InvocationContext; 26 import com.android.tradefed.invoker.TestInformation; 27 import com.android.tradefed.log.LogUtil.CLog; 28 import com.android.tradefed.targetprep.BaseTargetPreparer; 29 import com.android.tradefed.targetprep.BuildError; 30 import com.android.tradefed.targetprep.TargetSetupError; 31 import com.android.tradefed.util.AaptParser; 32 33 import java.io.File; 34 import java.io.FileNotFoundException; 35 import java.util.ArrayList; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.Set; 39 40 /** 41 * Installs device interaction helper implementations on the target. 42 * 43 * <p>Device interaction helpers allow xTS tests to drive aspects of a device that are not covered 44 * by public APIs but are necessary to make a test work, such as clicking a button to initiate 45 * printing in a test that exercises printing APIs. xTS tests running on the device will load 46 * concrete implementations at runtime using {@link HelperManager}, with a fallback to default 47 * implementations that interact with the default Android UI. See {@link 48 * DeviceInteractionHelperRule} for an example of connecting helpers to a test at runtime. 49 * 50 * <p>If device-specific implementations are needed, the device must contain a property listing the 51 * packages that will be searched at runtime. DeviceInteractionHelperInstaller will read this same 52 * property to determine which packages it will install. The default/fallback helpers are always 53 * installed. 54 * 55 * <p>See cts/libs/helpers and cts/helpers for more details and examples of device interaction 56 * helpers.. 57 */ 58 @OptionClass(alias = "device-interaction-helper", global_namespace = false) 59 public class DeviceInteractionHelperInstaller extends BaseTargetPreparer { 60 61 @Option( 62 name = "default-package", 63 description = "name of the package containing fallback device interaction helpers") 64 private String mDefaultHelperPackage = "com.android.cts.helpers.aosp"; 65 66 @Option( 67 name = "property-name", 68 description = "name of a device property listing necessary OEM helper packages") 69 private String mHelperPackagePropertyKey = "ro.vendor.cts_interaction_helper_packages"; 70 71 private Set<String> mInstalledPackages = new HashSet<String>(); 72 73 /** {@inheritDoc} */ 74 @Override setUp(ITestDevice device, IBuildInfo buildInfo)75 public void setUp(ITestDevice device, IBuildInfo buildInfo) 76 throws TargetSetupError, BuildError, DeviceNotAvailableException { 77 IInvocationContext context = new InvocationContext(); 78 context.addAllocatedDevice("device", device); 79 context.addDeviceBuildInfo("device", buildInfo); 80 TestInformation testInfo = 81 TestInformation.newBuilder().setInvocationContext(context).build(); 82 setUp(testInfo); 83 } 84 85 /** {@inheritDoc} */ 86 @Override setUp(TestInformation testInfo)87 public void setUp(TestInformation testInfo) 88 throws TargetSetupError, BuildError, DeviceNotAvailableException { 89 ITestDevice device = testInfo.getDevice(); 90 91 // Default helpers are always required. Push them first so we can fail early if missing. 92 String defaultHelperName = mDefaultHelperPackage + ".apk"; 93 try { 94 File defaultHelpers = testInfo.getDependencyFile(defaultHelperName, true); 95 installHelperApk(device, defaultHelpers, mDefaultHelperPackage); 96 } catch (FileNotFoundException e) { 97 throw new BuildError( 98 "Unable to find " 99 + defaultHelperName 100 + ". Make sure the file is present in your testcases directory or set " 101 + " an explicit path with default-interaction-helper-package.", 102 device.getDeviceDescriptor()); 103 } 104 105 // Parse the search path from the device property . It is not an error if the property is 106 // empty. This just means that the default helpers installed above will be used. 107 String searchPath = device.getProperty(mHelperPackagePropertyKey); 108 if (searchPath == null || searchPath.isEmpty()) { 109 return; 110 } 111 112 // Search locally for each package listed in the device package search property and install 113 // everything found. If a requested package is not found, that is not a fatal error because 114 // the particular test being run may not have required helpers from that package anyway. 115 for (String pkg : searchPath.split(":")) { 116 if (pkg.isEmpty()) { 117 continue; 118 } 119 if (mInstalledPackages.contains(pkg)) { 120 continue; // Already installed. 121 } 122 123 try { 124 String apkName = pkg + ".apk"; 125 File apkFile = testInfo.getDependencyFile(apkName, true); 126 checkApkFile(device, apkFile, pkg); 127 installHelperApk(device, apkFile, pkg); 128 } catch (FileNotFoundException e) { 129 CLog.w("Unable to find apk for %s", pkg); 130 } 131 } 132 } 133 134 /** {@inheritDoc} */ 135 @Override tearDown(TestInformation testInfo, Throwable t)136 public void tearDown(TestInformation testInfo, Throwable t) throws DeviceNotAvailableException { 137 ITestDevice device = testInfo.getDevice(); 138 for (String pkg : mInstalledPackages) { 139 String msg = device.uninstallPackage(pkg); 140 if (msg != null) { 141 CLog.w(String.format("Error uninstalling package '%s': %s", pkg, msg)); 142 } 143 } 144 } 145 146 /** 147 * Checks that an apk is readable and contains a valid package name. 148 * 149 * @param device the ITestDevice where the apk will be installed. 150 * @param apkFile the apk file to check. 151 * @param expectedPackage the expected name of the package in the apk. 152 * @throws BuildError if the apk isn't readable or isn't valid. 153 */ checkApkFile(ITestDevice device, File apkFile, String expectedPackage)154 private void checkApkFile(ITestDevice device, File apkFile, String expectedPackage) 155 throws BuildError { 156 String path = apkFile.getPath(); 157 if (!apkFile.canRead()) { 158 throw new BuildError( 159 "Helper " + path + " does not exist or is unreadable.", 160 device.getDeviceDescriptor()); 161 } 162 AaptParser parser = parseApk(apkFile); 163 if (parser == null) { 164 throw new BuildError( 165 "Unable to parse helper apk " + path, device.getDeviceDescriptor()); 166 } 167 String apkPackage = parser.getPackageName(); 168 if (apkPackage == null || apkPackage.isEmpty()) { 169 throw new BuildError( 170 "Unable to parse helper apk " + path, device.getDeviceDescriptor()); 171 } 172 if (!expectedPackage.equals(apkPackage)) { 173 throw new BuildError( 174 String.format( 175 "Helper apk %s declares package %s but was expected to declare %s", 176 path, apkPackage, expectedPackage), 177 device.getDeviceDescriptor()); 178 } 179 } 180 parseApk(File apkFile)181 protected AaptParser parseApk(File apkFile) { 182 return AaptParser.parse(apkFile); 183 } 184 185 /** Installs an apk containing DeviceInteractionHelper implementations. */ installHelperApk(ITestDevice device, File apkFile, String apkPackage)186 private void installHelperApk(ITestDevice device, File apkFile, String apkPackage) 187 throws BuildError, DeviceNotAvailableException, TargetSetupError { 188 if (apkFile == null) { 189 throw new BuildError("Invalid apk name", device.getDeviceDescriptor()); 190 } 191 CLog.logAndDisplay( 192 LogLevel.INFO, "Installing %s from %s", apkFile.getName(), apkFile.getPath()); 193 List<String> extraArgs = new ArrayList<String>(); 194 if (device.isAppEnumerationSupported()) { 195 extraArgs.add("--force-queryable"); 196 } 197 String msg = device.installPackage(apkFile, true, extraArgs.toArray(new String[] {})); 198 if (msg != null) { 199 throw new TargetSetupError( 200 String.format("Failed to install %s: %s", apkFile.getName(), msg), 201 device.getDeviceDescriptor()); 202 } 203 mInstalledPackages.add(apkPackage); 204 } 205 } 206