blob: c0602ee94d33632998c5873522dc1a4f2e710fcb [file] [log] [blame]
/*
* 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.tools.idea.sdk.wizard;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.tools.idea.sdk.SdkState;
import com.android.tools.idea.sdk.remote.UpdatablePkgInfo;
import com.android.tools.idea.wizard.dynamic.DialogWrapperHost;
import com.android.tools.idea.wizard.dynamic.DynamicWizard;
import com.android.tools.idea.wizard.dynamic.DynamicWizardPath;
import com.android.tools.idea.wizard.dynamic.ScopedStateStore;
import com.google.common.collect.Sets;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import org.jetbrains.android.actions.RunAndroidSdkManagerAction;
import org.jetbrains.android.sdk.AndroidSdkData;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
import java.util.Set;
import static com.android.tools.idea.wizard.WizardConstants.INSTALL_REQUESTS_KEY;
import static com.android.tools.idea.wizard.WizardConstants.SKIPPED_INSTALL_REQUESTS_KEY;
/**
* Provides a wizard which can install a list of items.
* <p>
* Example usage:
* <pre>
* {@code
public static class LaunchMe extends AnAction {
public LaunchMe() {
super("Launch SDK Quickfix Wizard");
}
@Override
public void actionPerformed(AnActionEvent e) {
List<IPkgDesc> requestedPackages = Lists.newArrayListWithCapacity(3);
FullRevision minBuildToolsRev = FullRevision.parseRevision(SdkConstants.MIN_BUILD_TOOLS_VERSION);
requestedPackages.add(PkgDesc.Builder.newBuildTool(minBuildToolsRev).create());
requestedPackages.add(PkgDesc.Builder.newPlatform(new AndroidVersion(19, null), new MajorRevision(1), minBuildToolsRev).create());
SdkQuickfixWizard sdkQuickfixWizard = new SdkQuickfixWizard(null, null, requestedPackages);
sdkQuickfixWizard.init();
sdkQuickfixWizard.show();
}
}
}
* </pre>
*/
public class SdkQuickfixWizard extends DynamicWizard {
private final List<IPkgDesc> myRequestedPackages;
private boolean myIsExiting = false;
public SdkQuickfixWizard(@Nullable Project project, @Nullable Module module, List<IPkgDesc> requestedPackages) {
this(project, module, requestedPackages, new DialogWrapperHost(project));
}
public SdkQuickfixWizard(@Nullable Project project, @Nullable Module module, List<IPkgDesc> requestedPackages, DialogWrapperHost host) {
super(project, module, "SDK Quickfix Installation", host);
myRequestedPackages = requestedPackages;
}
@Nullable
@Override
public Icon getIcon() {
return null;
}
@Override
public void init() {
ScopedStateStore state = getState();
addPath(new SdkQuickfixPath(getDisposable()));
Set<IPkgDesc> problems = findProblemPackages();
int selectedOption = 1; // Install all, default when there are no problems.
if (!problems.isEmpty()) {
StringBuilder warningBuilder = new StringBuilder("Due to your system configuration and the packages to be installed, \n" +
"it is likely that the following packages cannot be successfully installed while ");
warningBuilder.append(ApplicationNamesInfo.getInstance().getFullProductName());
warningBuilder.append(" is running. \n\nPlease exit and install the following packages using the standalone SDK manager:");
for (IPkgDesc problemPkg : problems) {
warningBuilder.append("\n -");
warningBuilder.append(problemPkg.getListDescription());
}
if (problems.size() == myRequestedPackages.size()) {
selectedOption = Messages.showDialog(getProject(), warningBuilder.toString(), "Warning", new String[]{
String.format("Exit %s and launch SDK Manager", ApplicationNamesInfo.getInstance().getProductName()),
"Attempt to install packages"}, 0, AllIcons.General.Warning);
}
else {
String[] options = new String[] {
String.format("Exit %s and launch SDK Manager", ApplicationNamesInfo.getInstance().getProductName()),
"Attempt to install all packages",
"Install safe packages"
};
selectedOption = Messages.showDialog(
getProject(), warningBuilder.toString(), "Warning",
options,
2, AllIcons.General.Warning);
}
}
if (selectedOption == 0) {
startSdkManagerAndExit();
myIsExiting = true;
return;
}
for (IPkgDesc desc : myRequestedPackages) {
if (selectedOption == 2 && problems.contains(desc)) {
state.listPush(SKIPPED_INSTALL_REQUESTS_KEY, desc);
}
else {
state.listPush(INSTALL_REQUESTS_KEY, desc);
}
}
super.init();
}
@Override
public boolean showAndGet() {
if (myIsExiting) {
Disposer.dispose(myHost.getDisposable());
return false;
}
return super.showAndGet();
}
private void startSdkManagerAndExit() {
// We know that the SDK exists since we already used it to look up the fact that a package is an upgrade.
//noinspection ConstantConditions
RunAndroidSdkManagerAction.runSpecificSdkManagerSynchronously(getProject(), AndroidSdkUtils.tryToChooseAndroidSdk().getLocation());
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
ApplicationManagerEx.getApplicationEx().exit(true, true);
}
});
}
/**
* Find packages that might not be able to be installed while studio is running.
* Currently this means packages that are upgrades on windows systems, since windows locks files that are in use.
* @return
*/
private Set<IPkgDesc> findProblemPackages() {
Set<IPkgDesc> result = Sets.newHashSet();
if (!SystemInfo.isWindows) {
return result;
}
SdkState state = SdkState.getInstance(AndroidSdkUtils.tryToChooseAndroidSdk());
state.loadSynchronously(SdkState.DEFAULT_EXPIRATION_PERIOD_MS, false, null, null, null, false);
Set<String> available = Sets.newHashSet();
for (UpdatablePkgInfo update : state.getPackages().getUpdatedPkgs()) {
if (update.hasRemote(false)) {
available.add(update.getRemote(false).getPkgDesc().getInstallId());
}
if (update.hasPreview()) {
available.add(update.getRemote(true).getPkgDesc().getInstallId());
}
}
for (IPkgDesc request : myRequestedPackages) {
if (available.contains(request.getInstallId())) {
// This is an update
result.add(request);
}
}
return result;
}
@Override
public void performFinishingActions() {
List<IPkgDesc> skipped = myState.get(SKIPPED_INSTALL_REQUESTS_KEY);
if (skipped != null && !skipped.isEmpty()) {
StringBuilder warningBuilder = new StringBuilder("The following packages were not installed.\n\n Would you like to exit ");
warningBuilder.append(ApplicationNamesInfo.getInstance().getFullProductName());
warningBuilder.append(" and install the following packages using the standalone SDK manager?");
for (IPkgDesc problemPkg : skipped) {
warningBuilder.append("\n");
warningBuilder.append(problemPkg.getListDescription());
}
String restartOption = String.format("Exit %s and launch SDK Manager", ApplicationNamesInfo.getInstance().getProductName());
int result = Messages.showDialog(getProject(), warningBuilder.toString(), "Warning", new String[]{restartOption, "Skip installation"},
0, AllIcons.General.Warning);
if (result == 0) {
startSdkManagerAndExit();
}
}
// We've already installed things, so clearly there's an SDK.
AndroidSdkData data = AndroidSdkUtils.tryToChooseAndroidSdk();
SdkState.getInstance(data).loadAsync(SdkState.DEFAULT_EXPIRATION_PERIOD_MS, false, null, null, null, true);
}
@NotNull
@Override
protected String getProgressTitle() {
return "Finishing install...";
}
@Override
protected String getWizardActionDescription() {
return "Provides a method for handling quickfix SDK installation actions";
}
private static class SdkQuickfixPath extends DynamicWizardPath {
private Disposable myDisposable;
private LicenseAgreementStep myLicenseAgreementStep;
public SdkQuickfixPath(Disposable disposable) {
myDisposable = disposable;
}
@Override
protected void init() {
myLicenseAgreementStep = new LicenseAgreementStep(myDisposable);
addStep(myLicenseAgreementStep);
addStep(new SmwOldApiDirectInstall(myDisposable));
}
@NotNull
@Override
public String getPathName() {
return "SDK Installation Quickfix";
}
@Override
public boolean performFinishingActions() {
myLicenseAgreementStep.performFinishingActions();
return true;
}
}
}