/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.build.gradle.internal.tasks; import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT; import com.android.build.gradle.internal.LoggerWrapper; import com.android.build.gradle.internal.TaskManager; import com.android.build.gradle.internal.scope.ConventionMappingHelper; import com.android.build.gradle.internal.scope.TaskConfigAction; import com.android.build.gradle.internal.scope.VariantScope; import com.android.build.gradle.internal.variant.ApkVariantData; import com.android.build.gradle.internal.variant.BaseVariantData; import com.android.build.gradle.internal.variant.BaseVariantOutputData; import com.android.builder.core.VariantConfiguration; import com.android.builder.internal.InstallUtils; import com.android.builder.sdk.SdkInfo; import com.android.builder.sdk.TargetInfo; import com.android.builder.testing.ConnectedDeviceProvider; import com.android.builder.testing.api.DeviceConfigProviderImpl; import com.android.builder.testing.api.DeviceConnector; import com.android.builder.testing.api.DeviceException; import com.android.builder.testing.api.DeviceProvider; import com.android.ide.common.build.SplitOutputMatcher; import com.android.ide.common.process.ProcessException; import com.android.ide.common.process.ProcessExecutor; import com.android.utils.FileUtils; import com.android.utils.ILogger; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import org.gradle.api.GradleException; import org.gradle.api.Task; import org.gradle.api.logging.LogLevel; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; import java.io.File; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; /** * Task installing an app variant. It looks at connected device and install the best matching * variant output on each device. */ public class InstallVariantTask extends BaseTask { private File adbExe; private File splitSelectExe; private ProcessExecutor processExecutor; private String projectName; private int timeOutInMs = 0; private Collection<String> installOptions; private BaseVariantData<? extends BaseVariantOutputData> variantData; public InstallVariantTask() { this.getOutputs().upToDateWhen(new Spec<Task>() { @Override public boolean isSatisfiedBy(Task task) { getLogger().debug("Install task is always run."); return false; } }); } @TaskAction public void install() throws DeviceException, ProcessException, InterruptedException { final ILogger iLogger = new LoggerWrapper(getLogger(), LogLevel.LIFECYCLE); DeviceProvider deviceProvider = new ConnectedDeviceProvider(getAdbExe(), iLogger); deviceProvider.init(); VariantConfiguration variantConfig = variantData.getVariantConfiguration(); String variantName = variantConfig.getFullName(); int successfulInstallCount = 0; List<? extends DeviceConnector> devices = deviceProvider.getDevices(); for (final DeviceConnector device : devices) { if (InstallUtils.checkDeviceApiLevel( device, variantConfig.getMinSdkVersion(), iLogger, projectName, variantName)) { // When InstallUtils.checkDeviceApiLevel returns false, it logs the reason. final List<File> apkFiles = SplitOutputMatcher.computeBestOutput(processExecutor, getSplitSelectExe(), new DeviceConfigProviderImpl(device), variantData.getOutputs(), variantData.getVariantConfiguration().getSupportedAbis()); if (apkFiles.isEmpty()) { getLogger().lifecycle( "Skipping device '{}' for '{}:{}': Could not find build of variant " + "which supports density {} and an ABI in {}", device.getName(), projectName, variantName, device.getDensity(), Joiner.on(", ").join(device.getAbis())); } else { getLogger().lifecycle( "Installing APK '{}' on '{}' for {}:{}", FileUtils.getNamesAsCommaSeparatedList(apkFiles), device.getName(), projectName, variantName); final Collection<String> extraArgs = Objects.firstNonNull(installOptions, ImmutableList.<String>of()); if (apkFiles.size() > 1 || device.getApiLevel() >= 21) { device.installPackages(apkFiles, extraArgs, getTimeOutInMs(), getILogger()); successfulInstallCount++; } else { device.installPackage(apkFiles.get(0), extraArgs, getTimeOutInMs(), getILogger()); successfulInstallCount++; } } } } if (successfulInstallCount == 0) { throw new GradleException("Failed to install on any devices."); } else { getLogger().quiet("Installed on {} {}.", successfulInstallCount, successfulInstallCount==1 ? "device" : "devices"); } } @InputFile public File getAdbExe() { return adbExe; } public void setAdbExe(File adbExe) { this.adbExe = adbExe; } @InputFile @Optional public File getSplitSelectExe() { return splitSelectExe; } public void setSplitSelectExe(File splitSelectExe) { this.splitSelectExe = splitSelectExe; } public ProcessExecutor getProcessExecutor() { return processExecutor; } public void setProcessExecutor(ProcessExecutor processExecutor) { this.processExecutor = processExecutor; } public String getProjectName() { return projectName; } public void setProjectName(String projectName) { this.projectName = projectName; } @Input public int getTimeOutInMs() { return timeOutInMs; } public void setTimeOutInMs(int timeOutInMs) { this.timeOutInMs = timeOutInMs; } @Input @Optional public Collection<String> getInstallOptions() { return installOptions; } public void setInstallOptions(Collection<String> installOptions) { this.installOptions = installOptions; } public BaseVariantData<? extends BaseVariantOutputData> getVariantData() { return variantData; } public void setVariantData( BaseVariantData<? extends BaseVariantOutputData> variantData) { this.variantData = variantData; } public static class ConfigAction implements TaskConfigAction<InstallVariantTask> { private final VariantScope scope; public ConfigAction(VariantScope scope) { this.scope = scope; } @Override public String getName() { return scope.getTaskName("install"); } @Override public Class<InstallVariantTask> getType() { return InstallVariantTask.class; } @Override public void execute(InstallVariantTask installTask) { installTask.setDescription( "Installs the " + scope.getVariantData().getDescription() + "."); installTask.setVariantName(scope.getVariantConfiguration().getFullName()); installTask.setGroup(TaskManager.INSTALL_GROUP); installTask.setProjectName(scope.getGlobalScope().getProject().getName()); installTask.setVariantData(scope.getVariantData()); installTask.setTimeOutInMs( scope.getGlobalScope().getExtension().getAdbOptions().getTimeOutInMs()); installTask.setInstallOptions( scope.getGlobalScope().getExtension().getAdbOptions().getInstallOptions()); installTask.setProcessExecutor( scope.getGlobalScope().getAndroidBuilder().getProcessExecutor()); ConventionMappingHelper.map(installTask, "adbExe", new Callable<File>() { @Override public File call() throws Exception { final SdkInfo info = scope.getGlobalScope().getSdkHandler().getSdkInfo(); return (info == null ? null : info.getAdb()); } }); ConventionMappingHelper.map(installTask, "splitSelectExe", new Callable<File>() { @Override public File call() throws Exception { final TargetInfo info = scope.getGlobalScope().getAndroidBuilder().getTargetInfo(); String path = info == null ? null : info.getBuildTools().getPath(SPLIT_SELECT); if (path != null) { File splitSelectExe = new File(path); return splitSelectExe.exists() ? splitSelectExe : null; } else { return null; } } }); ((ApkVariantData) scope.getVariantData()).installTask = installTask; } } }