com.android.tools.idea.avdmanager.HaxmAlert.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.avdmanager.HaxmAlert.java

Source

/*
 * 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.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);
        }
    }
}