/* * Copyright 2000-2010 JetBrains s.r.o. * * 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 org.jetbrains.android.run; import com.android.builder.model.AndroidArtifactOutput; import com.android.builder.model.Variant; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.IDevice; import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; import com.android.tools.idea.gradle.IdeaAndroidProject; import com.android.tools.idea.gradle.util.GradleUtil; import com.android.tools.idea.gradle.util.Projects; import com.android.tools.idea.model.AndroidModuleInfo; import com.intellij.CommonBundle; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.configurations.*; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.executors.DefaultRunExecutor; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ui.configuration.ClasspathEditor; import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.PathUtil; import com.intellij.util.containers.ConcurrentHashMap; import org.jdom.Element; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidFacetConfiguration; import org.jetbrains.android.facet.AndroidRootUtil; import org.jetbrains.android.sdk.AndroidPlatform; import org.jetbrains.android.sdk.AndroidSdkUtils; import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.*; public abstract class AndroidRunConfigurationBase extends ModuleBasedConfiguration<JavaRunConfigurationModule> { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.run.AndroidRunConfigurationBase"); private static final String GRADLE_SYNC_FAILED_ERR_MSG = "Gradle project sync failed. Please fix your project and try again."; /** * A map from launch configuration name to the state of devices at the time of the launch. * We want this list of devices persisted across launches, but not across invocations of studio, so we use a static variable. */ private static Map<String, DeviceStateAtLaunch> ourLastUsedDevices = new ConcurrentHashMap<String, DeviceStateAtLaunch>(); public String TARGET_SELECTION_MODE = TargetSelectionMode.EMULATOR.name(); public boolean USE_LAST_SELECTED_DEVICE = false; public String PREFERRED_AVD = ""; public boolean USE_COMMAND_LINE = true; public String COMMAND_LINE = ""; public boolean WIPE_USER_DATA = false; public boolean DISABLE_BOOT_ANIMATION = false; public String NETWORK_SPEED = "full"; public String NETWORK_LATENCY = "none"; public boolean CLEAR_LOGCAT = false; public boolean SHOW_LOGCAT_AUTOMATICALLY = true; public boolean FILTER_LOGCAT_AUTOMATICALLY = true; public AndroidRunConfigurationBase(final Project project, final ConfigurationFactory factory) { super(new JavaRunConfigurationModule(project, false), factory); } @Override public final void checkConfiguration() throws RuntimeConfigurationException { JavaRunConfigurationModule configurationModule = getConfigurationModule(); configurationModule.checkForWarning(); Module module = configurationModule.getModule(); if (module == null) { return; } Project project = module.getProject(); if (Projects.isGradleProjectWithoutModel(project)) { // This only shows an error message on the "Run Configuration" dialog, but does not prevent user from running app. throw new RuntimeConfigurationException(GRADLE_SYNC_FAILED_ERR_MSG); } AndroidFacet facet = AndroidFacet.getInstance(module); if (facet == null) { throw new RuntimeConfigurationError(AndroidBundle.message("android.no.facet.error")); } if (facet.isLibraryProject()) { Pair<Boolean, String> result = supportsRunningLibraryProjects(facet); if (!result.getFirst()) { throw new RuntimeConfigurationError(result.getSecond()); } } if (facet.getConfiguration().getAndroidPlatform() == null) { throw new RuntimeConfigurationError(AndroidBundle.message("select.platform.error")); } if (facet.getManifest() == null) { throw new RuntimeConfigurationError(AndroidBundle.message("android.manifest.not.found.error")); } if (PREFERRED_AVD.length() > 0) { AvdManager avdManager = facet.getAvdManagerSilently(); if (avdManager == null) { throw new RuntimeConfigurationError(AndroidBundle.message("avd.cannot.be.loaded.error")); } AvdInfo avdInfo = avdManager.getAvd(PREFERRED_AVD, false); if (avdInfo == null) { throw new RuntimeConfigurationError(AndroidBundle.message("avd.not.found.error", PREFERRED_AVD)); } if (avdInfo.getStatus() != AvdInfo.AvdStatus.OK) { String message = avdInfo.getErrorMessage(); message = AndroidBundle.message("avd.not.valid.error", PREFERRED_AVD) + (message != null ? ": " + message: "") + ". Try to repair it through AVD manager"; throw new RuntimeConfigurationError(message); } } checkConfiguration(facet); } /** Returns whether the configuration supports running library projects, and if it doesn't, then an explanation as to why it doesn't. */ protected abstract Pair<Boolean,String> supportsRunningLibraryProjects(@NotNull AndroidFacet facet); protected abstract void checkConfiguration(@NotNull AndroidFacet facet) throws RuntimeConfigurationException; @Override public Collection<Module> getValidModules() { final List<Module> result = new ArrayList<Module>(); Module[] modules = ModuleManager.getInstance(getProject()).getModules(); for (Module module : modules) { if (AndroidFacet.getInstance(module) != null) { result.add(module); } } return result; } @NotNull public TargetSelectionMode getTargetSelectionMode() { try { return TargetSelectionMode.valueOf(TARGET_SELECTION_MODE); } catch (IllegalArgumentException e) { LOG.info(e); return TargetSelectionMode.EMULATOR; } } public void setTargetSelectionMode(@NotNull TargetSelectionMode mode) { TARGET_SELECTION_MODE = mode.name(); } public void setDevicesUsedInLaunch(@NotNull Set<IDevice> usedDevices, @NotNull Set<IDevice> availableDevices) { ourLastUsedDevices.put(getName(), new DeviceStateAtLaunch(usedDevices, availableDevices)); } @Nullable public DeviceStateAtLaunch getDevicesUsedInLastLaunch() { return ourLastUsedDevices.get(getName()); } @Override public AndroidRunningState getState(@NotNull final Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException { final Module module = getConfigurationModule().getModule(); if (module == null) { throw new ExecutionException("Module is not found"); } final AndroidFacet facet = AndroidFacet.getInstance(module); if (facet == null) { throw new ExecutionException(AndroidBundle.message("no.facet.error", module.getName())); } Project project = env.getProject(); if (Projects.isGradleProjectWithoutModel(project)) { // This prevents user from running the app. throw new ExecutionException(GRADLE_SYNC_FAILED_ERR_MSG); } IdeaAndroidProject ideaAndroidProject = facet.getIdeaAndroidProject(); if (ideaAndroidProject != null) { Variant variant = ideaAndroidProject.getSelectedVariant(); if (!variant.getMainArtifact().isSigned()) { AndroidArtifactOutput output = GradleUtil.getOutput(variant.getMainArtifact()); String message = AndroidBundle.message("run.error.apk.not.signed", output.getOutputFile().getName()); Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle()); return null; } } AndroidFacetConfiguration configuration = facet.getConfiguration(); AndroidPlatform platform = configuration.getAndroidPlatform(); if (platform == null) { Messages.showErrorDialog(project, AndroidBundle.message("specify.platform.error"), CommonBundle.getErrorTitle()); ModulesConfigurator.showDialog(project, module.getName(), ClasspathEditor.NAME); return null; } boolean debug = DefaultDebugExecutor.EXECUTOR_ID.equals(executor.getId()); boolean nonDebuggableOnDevice = false; if (debug) { Boolean isDebuggable = AndroidModuleInfo.get(facet).isDebuggable(); nonDebuggableOnDevice = isDebuggable != null && !isDebuggable; if (!AndroidSdkUtils.activateDdmsIfNecessary(facet.getModule().getProject())) { return null; } } if (AndroidSdkUtils.getDebugBridge(getProject()) == null) return null; TargetChooser targetChooser = null; switch (getTargetSelectionMode()) { case SHOW_DIALOG: targetChooser = new ManualTargetChooser(); break; case EMULATOR: targetChooser = new EmulatorTargetChooser(PREFERRED_AVD.length() > 0 ? PREFERRED_AVD : null); break; case USB_DEVICE: targetChooser = new UsbDeviceTargetChooser(); break; default: assert false : "Unknown target selection mode " + TARGET_SELECTION_MODE; break; } AndroidApplicationLauncher applicationLauncher = getApplicationLauncher(facet); if (applicationLauncher != null) { final boolean supportMultipleDevices = supportMultipleDevices() && executor.getId().equals(DefaultRunExecutor.EXECUTOR_ID); return new AndroidRunningState(env, facet, targetChooser, computeCommandLine(), applicationLauncher, supportMultipleDevices, CLEAR_LOGCAT, this, nonDebuggableOnDevice); } return null; } @Nullable protected static Pair<File, String> getCopyOfCompilerManifestFile(@NotNull AndroidFacet facet, @Nullable ProcessHandler processHandler) { final VirtualFile manifestFile = AndroidRootUtil.getCustomManifestFileForCompiler(facet); if (manifestFile == null) { return null; } File tmpDir = null; try { tmpDir = FileUtil.createTempDirectory("android_manifest_file_for_execution", "tmp"); final File manifestCopy = new File(tmpDir, manifestFile.getName()); FileUtil.copy(new File(manifestFile.getPath()), manifestCopy); //noinspection ConstantConditions return Pair.create(manifestCopy, PathUtil.getLocalPath(manifestFile)); } catch (IOException e) { if (processHandler != null) { processHandler.notifyTextAvailable("I/O error: " + e.getMessage(), ProcessOutputTypes.STDERR); } LOG.info(e); if (tmpDir != null) { FileUtil.delete(tmpDir); } return null; } } private String computeCommandLine() { StringBuilder result = new StringBuilder(); result.append("-netspeed ").append(NETWORK_SPEED).append(' '); result.append("-netdelay ").append(NETWORK_LATENCY).append(' '); if (WIPE_USER_DATA) { result.append("-wipe-data "); } if (DISABLE_BOOT_ANIMATION) { result.append("-no-boot-anim "); } if (USE_COMMAND_LINE) { result.append(COMMAND_LINE); } int last = result.length() - 1; if (result.charAt(last) == ' ') { result.deleteCharAt(last); } return result.toString(); } @NotNull protected abstract ConsoleView attachConsole(AndroidRunningState state, Executor executor) throws ExecutionException; @Nullable protected abstract AndroidApplicationLauncher getApplicationLauncher(AndroidFacet facet); protected abstract boolean supportMultipleDevices(); @Override public void readExternal(Element element) throws InvalidDataException { super.readExternal(element); readModule(element); DefaultJDOMExternalizer.readExternal(this, element); } @Override public void writeExternal(Element element) throws WriteExternalException { super.writeExternal(element); writeModule(element); DefaultJDOMExternalizer.writeExternal(this, element); } }