Java tutorial
/* * Copyright (C) 2015 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.tools.idea.avdmanager; import com.android.SdkConstants; import com.android.repository.Revision; import com.android.tools.idea.sdk.wizard.SdkQuickfixUtils; import com.android.tools.idea.sdk.wizard.HaxmWizard; import com.android.tools.idea.wizard.model.ModelWizardDialog; import com.google.common.collect.ImmutableList; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.CapturingAnsiEscapesAwareProcessHandler; import com.intellij.execution.process.ProcessOutput; import com.intellij.ide.BrowserUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.io.FileUtilRt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * Solution strings used in {@link AccelerationErrorCode}, and associated Runnables to fix them. */ public class AccelerationErrorSolution { private static final Logger LOG = Logger.getInstance(AccelerationErrorNotificationPanel.class); private static final Revision KARMIC_KERNEL = Revision.parseRevision("2.6.31"); private static final String KVM_INSTRUCTIONS = "https://help.ubuntu.com/community/KVM/Installation"; static final String SOLUTION_NESTED_VIRTUAL_MACHINE = "Unfortunately, the Android Emulator can't support virtual machine acceleration from within a virtual machine.\n" + "Here are some of your options:\n" + " 1) Use a physical device for testing\n" + " 2) Start the emulator on a non-virtualized operating system\n" + " 3) Use an Android Virtual Device based on an ARM system image (This is 10x slower than hardware accelerated virtualization)\n"; static final String SOLUTION_ACCELERATION_NOT_SUPPORTED = "Unfortunately, your computer does not support hardware accelerated virtualization.\n" + "Here are some of your options:\n" + " 1) Use a physical device for testing\n" + " 2) Develop on a Windows/OSX computer with an Intel processor that supports VT-x and NX\n" + " 3) Develop on a Linux computer that supports VT-x or SVM\n" + " 4) Use an Android Virtual Device based on an ARM system image\n" + " (This is 10x slower than hardware accelerated virtualization)\n"; static final String SOLUTION_TURN_OFF_HYPER_V = "Unfortunately, you cannot have Hyper-V running and use the emulator.\n" + "Here is what you can do:\n" + " 1) Start a command prompt as Administrator\n" + " 2) Run the following command: C:\\Windows\\system32> bcdedit /set hypervisorlaunchtype off\n" + " 3) Reboot your machine.\n"; static final String SOLUTION_REBOOT_AFTER_TURNING_HYPER_V_OFF = "Hyper-V was successfully turned off. However a system restart is required for this to take effect.\n\n" + "Do you want to reboot now?\n"; private static final AtomicBoolean ourRebootRequestedAsync = new AtomicBoolean(false); /** * Solution to problems that we can fix from Android Studio. */ public enum SolutionCode { NONE("Troubleshoot"), DOWNLOAD_EMULATOR("Install Emulator"), UPDATE_EMULATOR( "Update Emulator"), UPDATE_PLATFORM_TOOLS("Update Platform Tools"), UPDATE_SYSTEM_IMAGES( "Update System Images"), INSTALL_KVM("Install KVM"), INSTALL_HAXM( "Install Haxm"), REINSTALL_HAXM( "Reinstall Haxm"), TURNOFF_HYPER_V("Turn off Hyper-V"); private final String myDescription; public String getDescription() { return myDescription; } SolutionCode(@NotNull String shortDescription) { myDescription = shortDescription; } } /** * Returns a {@link Runnable} with code for applying the solution for a given problem {@link AccelerationErrorCode}. * In some cases all we can do is present some text to the user in which case the returned {@link Runnable} will simply * display a dialog box with text that the user can use as a guide for solving the problem on their own.</br> * In other cases we can install the component that is required.</br> * It is guaranteed that one and only one of the callbacks {@code refresh} and {@code cancel} is called if they are both supplied. * @param error the problem we are creating an action for * @param project the project (may be null but this circumvents certain updates) * @param refresh a {@link Runnable} to execute after a change has been applied * @param cancel a {@link Runnable} to execute if no change was applied * @return a {link Runnable} to "fix" or display a "fix" for/to the user */ public static Runnable getActionForFix(@NotNull AccelerationErrorCode error, @Nullable Project project, @Nullable Runnable refresh, @Nullable Runnable cancel) { return new AccelerationErrorSolution(error, project, refresh, cancel).getAction(); } private final AccelerationErrorCode myError; private final Project myProject; private final Runnable myRefresh; private final Runnable myCancel; private boolean myChangesMade; private AccelerationErrorSolution(@NotNull AccelerationErrorCode error, @Nullable Project project, @Nullable Runnable refresh, @Nullable Runnable cancel) { assert error != AccelerationErrorCode.ALREADY_INSTALLED; myError = error; myProject = project; myRefresh = refresh; myCancel = cancel; } private Runnable getAction() { switch (myError.getSolution()) { case DOWNLOAD_EMULATOR: case UPDATE_EMULATOR: return new Runnable() { @Override public void run() { try { showQuickFix(ImmutableList.of(SdkConstants.FD_TOOLS, SdkConstants.FD_PLATFORM_TOOLS)); } finally { reportBack(); } } }; case UPDATE_PLATFORM_TOOLS: return new Runnable() { @Override public void run() { try { showQuickFix(ImmutableList.of(SdkConstants.FD_PLATFORM_TOOLS)); } finally { reportBack(); } } }; case UPDATE_SYSTEM_IMAGES: return new Runnable() { @Override public void run() { try { AvdManagerConnection avdManager = AvdManagerConnection.getDefaultAvdManagerConnection(); showQuickFix(avdManager.getSystemImageUpdates()); } finally { reportBack(); } } }; case INSTALL_KVM: return new Runnable() { @Override public void run() { try { GeneralCommandLine install = createKvmInstallCommand(); if (install == null) { BrowserUtil.browse(KVM_INSTRUCTIONS, myProject); } else { String text = String.format( "Linux systems vary a great deal; the installation steps we will attempt may not work in your particular scenario.\n\n" + "The steps are:\n\n" + " %1$s\n\n" + "If you prefer, you can skip this step and perform the KVM installation steps on your own.\n\n" + "There might be more details at: %2$s\n", install.getCommandLineString(), KVM_INSTRUCTIONS); int response = Messages.showDialog(text, myError.getSolution().getDescription(), new String[] { "Skip", "Proceed" }, 1, Messages.getQuestionIcon()); if (response == 1) { try { execute(install); myChangesMade = true; } catch (ExecutionException ex) { LOG.error(ex); BrowserUtil.browse(KVM_INSTRUCTIONS, myProject); Messages.showWarningDialog(myProject, "Please install KVM on your own", "Installation Failed"); } } else { BrowserUtil.browse(KVM_INSTRUCTIONS, myProject); } } } finally { reportBack(); } } }; case INSTALL_HAXM: case REINSTALL_HAXM: return new Runnable() { @Override public void run() { try { HaxmWizard wizard = new HaxmWizard(false); wizard.init(); myChangesMade = wizard.showAndGet(); } finally { reportBack(); } } }; case TURNOFF_HYPER_V: return new Runnable() { @Override public void run() { try { GeneralCommandLine turnHyperVOff = new ElevatedCommandLine(); turnHyperVOff.setExePath("bcdedit"); turnHyperVOff.addParameters("/set", "hypervisorlaunchtype", "off"); turnHyperVOff.setWorkDirectory(FileUtilRt.getTempDirectory()); try { execute(turnHyperVOff); promptAndReboot(SOLUTION_REBOOT_AFTER_TURNING_HYPER_V_OFF); } catch (ExecutionException ex) { LOG.error(ex); Messages.showWarningDialog(myProject, SOLUTION_TURN_OFF_HYPER_V, "Operation Failed"); } } finally { reportBack(); } } }; default: return new Runnable() { @Override public void run() { try { Messages.showWarningDialog(myProject, myError.getSolutionMessage(), myError.getSolution().getDescription()); } finally { reportBack(); } } }; } } /** * Prompts the user to reboot now, and performs the reboot if accepted. * HAXM Installer may need a reboot only on Windows, so this method is intended to work only on Windows * and only for HAXM installer use case * * @param prompt the message to display to the user * @exception ExecutionException if the shutdown command fails to execute * @return No return value */ public static void promptAndReboot(@NotNull String prompt) throws ExecutionException { int response = Messages.showOkCancelDialog((Project) null, prompt, "Reboot Now", Messages.getQuestionIcon()); if (response == Messages.OK) { GeneralCommandLine reboot = new ElevatedCommandLine(); reboot.setExePath("shutdown"); reboot.addParameters("/g", "/t", "10"); // shutdown & restart after a 10 sec delay reboot.setWorkDirectory(FileUtilRt.getTempDirectory()); execute(reboot); } } /** * Async version of {@link #promptAndReboot(String)}, which uses {@link ModalityState} to determine when to prompt for the reboot. * This is required when a reboot may be requested from multiple places during one wizard execution, and: * - we don't want the prompt to appear before the wizard finishes * - we don't want the prompt to appear several times * * If one reboot request is already queued, subsequent calls will be a no-op even when called with a different * {@link ModalityState} or reboot message. Once a prompt is released to the user and the choice has been made, * another call to this method will queue another reboot request. * * @param prompt the message to display to the user * @param modality ModalityState which determines when the reboot prompt will actually appear * @return No return value */ public static void promptAndRebootAsync(@NotNull String prompt, @NotNull ModalityState modality) { if (ourRebootRequestedAsync.compareAndSet(false, true)) { ApplicationManager.getApplication().invokeLater(() -> { try { promptAndReboot(prompt); } catch (ExecutionException e) { LOG.warn("Automatic reboot attempt failed due to an exception", e); Messages.showErrorDialog("Reboot attempt failed. Please reboot manually", "Automatic Reboot"); } ourRebootRequestedAsync.set(false); }, modality); } } private static String execute(@NotNull String command, String... parameters) throws ExecutionException { return execute(generateCommand(command, parameters)); } private static GeneralCommandLine generateCommand(@NotNull String command, String... parameters) { GeneralCommandLine commandLine = new GeneralCommandLine(); commandLine.setExePath(command); commandLine.addParameters(parameters); return commandLine; } private static String execute(@NotNull GeneralCommandLine commandLine) throws ExecutionException { int exitValue; CapturingAnsiEscapesAwareProcessHandler process = new CapturingAnsiEscapesAwareProcessHandler(commandLine); ProcessOutput output = process.runProcess(); exitValue = output.getExitCode(); if (exitValue == 0) { return output.getStdout(); } else { throw new ExecutionException(String.format("Error running: %1$s", process.getCommandLine())); } } @Nullable private static GeneralCommandLine createKvmInstallCommand() { try { String version = execute("uname", "-r"); Revision revision = toRevision(version); if (revision.compareTo(KARMIC_KERNEL) <= 0) { return generateCommand("gksudo", "aptitude -y", "install", "kvm", "libvirt-bin", "ubuntu-vm-builder", "bridge-utils"); } else { return generateCommand("gksudo", "apt-get --assume-yes", "install", "qemu-kvm", "libvirt-bin", "ubuntu-vm-builder", "bridge-utils"); } } catch (ExecutionException ex) { LOG.error(ex); } catch (NumberFormatException ex) { LOG.error(ex); } return null; } private static Revision toRevision(@NotNull String version) { int index = version.indexOf('-'); if (index > 0) { version = version.substring(0, index); } return Revision.parseRevision(version); } private void showQuickFix(@NotNull List<String> requested) { ModelWizardDialog sdkQuickfixWizard = SdkQuickfixUtils.createDialogForPaths(myProject, requested); if (sdkQuickfixWizard != null) { sdkQuickfixWizard.show(); if (sdkQuickfixWizard.getExitCode() == DialogWrapper.OK_EXIT_CODE) { myChangesMade = true; } } } private void reportBack() { Runnable reporter = myChangesMade ? myRefresh : myCancel; if (reporter != null) { ApplicationManager.getApplication().invokeLater(reporter); } } }