blob: d819c97d154975e722e4648e90e64f6445c39e46 [file] [log] [blame]
/*
* 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.sdklib.SdkVersionInfo;
import com.android.sdklib.devices.Abi;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.tools.idea.sdk.wizard.LicenseAgreementStep;
import com.android.tools.idea.welcome.install.*;
import com.android.tools.idea.welcome.wizard.ProgressStep;
import com.android.tools.idea.wizard.*;
import com.android.tools.idea.wizard.dynamic.*;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.io.Closeables;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.execution.util.ExecUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.ui.HyperlinkAdapter;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.View;
import java.awt.*;
import java.io.*;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Component for displaying an alert on the installation state of HAXM/KVM.
*/
public class HaxmAlert extends JPanel {
private JBLabel myWarningMessage;
private HyperlinkLabel myErrorInstructionsLink;
private HyperlinkListener myErrorLinkListener;
SystemImageDescription myImageDescription;
public HaxmAlert() {
myErrorInstructionsLink = new HyperlinkLabel();
myWarningMessage = new JBLabel() {
@Override
public Dimension getPreferredSize() {
// Since this contains auto-wrapped text, the preferred height will not be set until repaint(). The below will set it as soon
// as the actual width is known. This allows the wizard dialog to be set to the correct size even before this step is shown.
final View view = (View)getClientProperty("html");
Component parent = getParent();
if (view != null && parent != null && parent.getWidth() > 0) {
view.setSize(parent.getWidth(), 0);
return new Dimension((int)view.getPreferredSpan(View.X_AXIS), (int)view.getPreferredSpan(View.Y_AXIS));
}
return super.getPreferredSize();
}
};
this.setLayout(new GridLayoutManager(2, 1));
GridConstraints constraints = new GridConstraints();
constraints.setAnchor(GridConstraints.ANCHOR_WEST);
add(myWarningMessage, constraints);
constraints.setRow(1);
add(myErrorInstructionsLink, constraints);
myErrorInstructionsLink.setOpaque(false);
myWarningMessage.setForeground(JBColor.RED);
myWarningMessage.setHorizontalAlignment(SwingConstants.LEFT);
setOpaque(false);
this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Recommendation"),
BorderFactory.createEmptyBorder(0, 5, 3, 5)));
}
public void setSystemImageDescription(SystemImageDescription description) {
myImageDescription = description;
refresh();
}
private void refresh() {
if (myImageDescription == null) {
setVisible(false);
return;
}
boolean hasLink = false;
StringBuilder warningTextBuilder = new StringBuilder();
if (isIntel()) {
if (!myImageDescription.getAbiType().startsWith(Abi.X86.toString())) {
warningTextBuilder.append("Consider using an x86 system image for better emulation performance.<br>");
} else {
HaxmState haxmState = getHaxmState(false);
if (haxmState == HaxmState.NOT_INSTALLED) {
if (SystemInfo.isLinux) {
warningTextBuilder.append("Enable Linux KVM for better emulation performance.<br>");
myErrorInstructionsLink.setHyperlinkTarget(FirstRunWizardDefaults.KVM_LINUX_INSTALL_URL);
myErrorInstructionsLink.setHtmlText("<a>KVM Instructions</a>");
if (myErrorLinkListener != null) {
myErrorInstructionsLink.removeHyperlinkListener(myErrorLinkListener);
}
hasLink = true;
}
else if (Haxm.canRun()) {
warningTextBuilder.append("Install Intel HAXM for better emulation performance.<br>");
setupDownloadLink();
hasLink = true;
}
}
else if (haxmState == HaxmState.NOT_LATEST) {
warningTextBuilder.append("Newer HAXM Version Available<br>");
setupDownloadLink();
hasLink = true;
}
}
}
if (myImageDescription.getVersion().getApiLevel() < SdkVersionInfo.LOWEST_ACTIVE_API) {
warningTextBuilder.append("This API Level is Deprecated<br>");
}
String warningText = warningTextBuilder.toString();
if (!warningText.isEmpty()) {
warningTextBuilder.insert(0, "<html>");
warningTextBuilder.append("</html>");
myWarningMessage.setText(warningTextBuilder.toString());
setVisible(true);
myErrorInstructionsLink.setVisible(hasLink);
} else {
setVisible(false);
}
Window window = SwingUtilities.getWindowAncestor(this);
if (window != null) {
window.pack();
}
}
private void setupDownloadLink() {
myErrorInstructionsLink.setHyperlinkTarget(null);
myErrorInstructionsLink.setHtmlText("<a>Download and install HAXM<a>");
if (myErrorLinkListener != null) {
myErrorInstructionsLink.removeHyperlinkListener(myErrorLinkListener);
}
myErrorLinkListener =
new HyperlinkAdapter() {
@Override
protected void hyperlinkActivated(HyperlinkEvent e) {
HaxmWizard wizard = new HaxmWizard();
wizard.init();
wizard.show();
getHaxmState(true);
refresh();
}
};
myErrorInstructionsLink.addHyperlinkListener(myErrorLinkListener);
}
private boolean isIntel() {
if (SystemInfo.isMac) {
return true;
} else if (SystemInfo.isLinux) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("/proc/cpuinfo"));
String line = br.readLine();
while (br.ready()) {
if (line.startsWith("vendor_id") && line.endsWith("GenuineIntel")) {
return true;
}
line = br.readLine();
}
} catch (FileNotFoundException e) {
Logger.getInstance(getClass()).warn("/proc/cpuinfo not found, assuming non-intel CPU");
return false;
} catch (IOException e) {
Logger.getInstance(getClass()).warn("Error reading /proc/cpuinfo, assuming non-intel CPU");
return false;
} finally {
Closeables.closeQuietly(br);
}
return false;
} else if (SystemInfo.isWindows) {
String id = System.getenv().get("PROCESSOR_IDENTIFIER");
return id != null && id.contains("GenuineIntel");
}
return false;
}
enum HaxmState { NOT_INITIALIZED, INSTALLED, NOT_INSTALLED, NOT_LATEST }
private static HaxmState ourHaxmState = HaxmState.NOT_INITIALIZED;
private static HaxmState getHaxmState(boolean forceRefresh) {
if (ourHaxmState == HaxmState.NOT_INITIALIZED || forceRefresh) {
ourHaxmState = computeHaxmState();
}
return ourHaxmState;
}
private static HaxmState computeHaxmState() {
boolean found = false;
try {
if (SystemInfo.isMac) {
@SuppressWarnings("SpellCheckingInspection")
String output = ExecUtil.execAndReadLine("/usr/sbin/kextstat", "-l", "-b", "com.intel.kext.intelhaxm");
if (output != null && !output.isEmpty()) {
Pattern pattern = Pattern.compile("com\\.intel\\.kext\\.intelhaxm( \\((.+)\\))?");
Matcher matcher = pattern.matcher(output);
if (matcher.find()) {
found = true;
}
}
} else if (SystemInfo.isWindows) {
@SuppressWarnings("SpellCheckingInspection") ProcessOutput
processOutput = ExecUtil.execAndGetOutput(ImmutableList.of("sc", "query", "intelhaxm"), null);
found = Iterables.all(processOutput.getStdoutLines(), new Predicate<String>() {
@Override
public boolean apply(String input) {
return input == null || !input.contains("does not exist");
}
});
} else if (SystemInfo.isUnix) {
File kvm = new File("/dev/kvm");
return kvm.exists() ? HaxmState.INSTALLED : HaxmState.NOT_INSTALLED;
} else {
assert !SystemInfo.isLinux; // should be covered by SystemInfo.isUnix
return HaxmState.NOT_INSTALLED;
}
} catch (ExecutionException e) {
return HaxmState.NOT_INSTALLED;
}
if (found) {
try {
FullRevision revision = Haxm.getInstalledVersion(AndroidSdkUtils.tryToChooseAndroidSdk().getLocation());
FullRevision current = new FullRevision(1, 1, 1);
if (revision.compareTo(current) < 0) {
// We have the new version number, as well as the currently installed
// version number here, which we could use to make a better error message.
// However, these versions do not correspond to the version number we show
// in the SDK manager (e.g. in the SDK version manager we show "5"
// and the corresponding kernel stat version number is 1.1.1.
return HaxmState.NOT_LATEST;
}
} catch (WizardException e) {
return HaxmState.NOT_INSTALLED;
}
return HaxmState.INSTALLED;
}
return HaxmState.NOT_INSTALLED;
}
private class HaxmPath extends DynamicWizardPath {
DynamicWizardHost myHost;
public HaxmPath(DynamicWizardHost host) {
myHost = host;
}
@Override
protected void init() {
ScopedStateStore.Key<Boolean> canShow = ScopedStateStore.createKey("ShowHaxmSteps",
ScopedStateStore.Scope.PATH, Boolean.class);
myState.put(canShow, true);
Haxm haxm = new Haxm(getState(), canShow);
for (IPkgDesc desc : haxm.getRequiredSdkPackages(null)) {
myState.listPush(WizardConstants.INSTALL_REQUESTS_KEY, desc);
}
for (DynamicWizardStep step : haxm.createSteps()) {
addStep(step);
}
addStep(new LicenseAgreementStep(getWizard().getDisposable()));
ProgressStep progressStep = new SetupProgressStep(getWizard().getDisposable(), haxm, myHost);
addStep(progressStep);
haxm.init(progressStep);
}
@NotNull
@Override
public String getPathName() {
return "Haxm Path";
}
@Override
public boolean performFinishingActions() {
return false;
}
}
private class HaxmWizard extends DynamicWizard {
public HaxmWizard() {
super(null, null, "HAXM");
HaxmPath path = new HaxmPath(myHost);
addPath(path);
}
@Override
public void performFinishingActions() {
// Nothing. Handled by SetupProgressStep.
}
@NotNull
@Override
protected String getProgressTitle() {
return "Finishing install...";
}
@Override
protected String getWizardActionDescription() {
return "HAXM Installation";
}
}
private static class SetupProgressStep extends ProgressStep {
private Haxm myHaxm;
private final AtomicBoolean myIsBusy = new AtomicBoolean(false);
private DynamicWizardHost myHost;
public SetupProgressStep(Disposable parentDisposable, Haxm haxm, DynamicWizardHost host) {
super(parentDisposable);
myHaxm = haxm;
myHost = host;
}
@Override
public boolean canGoNext() {
return false;
}
@Override
protected void execute() {
myIsBusy.set(true);
myHost.runSensitiveOperation(getProgressIndicator(), true, new Runnable() {
@Override
public void run() {
try {
setupHaxm();
}
catch (Exception e) {
Logger.getInstance(getClass()).error(e);
showConsole();
print(e.getMessage() + "\n", ConsoleViewContentType.ERROR_OUTPUT);
}
finally {
myIsBusy.set(false);
}
}
});
}
@Override
public boolean canGoPrevious() {
return false;
}
private void setupHaxm() throws IOException {
final InstallContext installContext = new InstallContext(
FileUtil.createTempDirectory("AndroidStudio", "Haxm", true), this);
final File destination = AndroidSdkUtils.tryToChooseAndroidSdk().getLocation();
final Collection<? extends InstallableComponent> selectedComponents = Lists.newArrayList(myHaxm);
installContext.print("Looking for SDK updates...\n", ConsoleViewContentType.NORMAL_OUTPUT);
// Assume install and configure take approximately the same time; assign 0.5 progressRatio to each
InstallComponentsOperation install =
new InstallComponentsOperation(installContext, selectedComponents, new ComponentInstaller(null), 0.5);
try {
install.then(InstallOperation.wrap(installContext, new Function<File, File>() {
@Override
public File apply(@Nullable File input) {
myHaxm.configure(installContext, input);
return input;
}
}, 0.5)).execute(destination);
}
catch (InstallationCancelledException e) {
installContext.print("Android Studio setup was canceled", ConsoleViewContentType.ERROR_OUTPUT);
}
catch (WizardException e) {
throw new RuntimeException(e);
}
installContext.print("Done", ConsoleViewContentType.NORMAL_OUTPUT);
}
}
}