blob: 48667bea03bf2c1b3f484d9e890e4a6b39f12b77 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.internal.actions;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.io.FileOp;
import com.android.sdklib.repository.ISdkChangeListener;
import com.android.utils.GrabProcessOutput;
import com.android.utils.GrabProcessOutput.IProcessOutput;
import com.android.utils.GrabProcessOutput.Wait;
import com.android.sdkuilib.repository.SdkUpdaterWindow;
import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Delegate for the toolbar/menu action "Android SDK Manager".
* It displays the Android SDK Manager.
*/
public class SdkManagerAction implements IWorkbenchWindowActionDelegate, IObjectActionDelegate {
@Override
public void dispose() {
// nothing to dispose.
}
@Override
public void init(IWorkbenchWindow window) {
// no init
}
@Override
public void run(IAction action) {
// Although orthogonal to the sdk manager action, this is a good time
// to check whether the SDK has changed on disk.
AdtPlugin.getDefault().refreshSdk();
if (!openExternalSdkManager()) {
// If we failed to execute the sdk manager, check the SDK location.
// If it's not properly set, the check will display a dialog to state
// so to the user and a link to the prefs.
// Here's it's ok to call checkSdkLocationAndId() since it will not try
// to run the SdkManagerAction (it might run openExternalSdkManager though.)
// If checkSdkLocationAndId tries to open the SDK Manager, it end up using
// the internal one.
if (AdtPlugin.getDefault().checkSdkLocationAndId()) {
// The SDK check was successful, yet the sdk manager fail to launch anyway.
AdtPlugin.displayError(
"Android SDK",
"Failed to run the Android SDK Manager. Check the Android Console View for details.");
}
}
}
/**
* A custom implementation of {@link ProgressMonitorDialog} that allows us
* to rename the "Cancel" button to "Close" from the internal task.
*/
private static class CloseableProgressMonitorDialog extends ProgressMonitorDialog {
public CloseableProgressMonitorDialog(Shell parent) {
super(parent);
}
public void changeCancelToClose() {
if (cancel != null && !cancel.isDisposed()) {
Display display = getShell() == null ? null : getShell().getDisplay();
if (display != null) {
display.syncExec(new Runnable() {
@Override
public void run() {
if (cancel != null && !cancel.isDisposed()) {
cancel.setText(IDialogConstants.CLOSE_LABEL);
}
}
});
}
}
}
}
/**
* Opens the SDK Manager as an external application.
* This call is asynchronous, it doesn't wait for the manager to be closed.
* <p/>
* Important: this method must NOT invoke {@link AdtPlugin#checkSdkLocationAndId}
* (in any of its variations) since the dialog uses this method to invoke the sdk
* manager if needed.
*
* @return True if the application was found and executed. False if it could not
* be located or could not be launched.
*/
public static boolean openExternalSdkManager() {
// On windows this takes a couple seconds and it's not clear the launch action
// has been invoked. To prevent the user from randomly clicking the "open sdk manager"
// button multiple times, show a progress window that will automatically close
// after a couple seconds.
// By default openExternalSdkManager will return false.
final AtomicBoolean returnValue = new AtomicBoolean(false);
final CloseableProgressMonitorDialog p =
new CloseableProgressMonitorDialog(AdtPlugin.getShell());
p.setOpenOnRun(true);
try {
p.run(true /*fork*/, true /*cancelable*/, new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
// Get the SDK locatiom from the current SDK or as fallback
// directly from the ADT preferences.
Sdk sdk = Sdk.getCurrent();
String osSdkLocation = sdk == null ? null : sdk.getSdkOsLocation();
if (osSdkLocation == null || !new File(osSdkLocation).isDirectory()) {
osSdkLocation = AdtPrefs.getPrefs().getOsSdkFolder();
}
// If there's no SDK location or it's not a valid directory,
// there's nothing we can do. When this is invoked from run()
// the checkSdkLocationAndId method call should display a dialog
// telling the user to configure the preferences.
if (osSdkLocation == null || !new File(osSdkLocation).isDirectory()) {
return;
}
final int numIter = 30; //30*100=3s to wait for window
final int sleepMs = 100;
monitor.beginTask("Starting Android SDK Manager", numIter);
File androidBat = FileOp.append(
osSdkLocation,
SdkConstants.FD_TOOLS,
SdkConstants.androidCmdName());
if (!androidBat.exists()) {
AdtPlugin.printErrorToConsole("SDK Manager",
"Missing %s file in Android SDK.", SdkConstants.androidCmdName());
return;
}
if (monitor.isCanceled()) {
// Canceled by user; return true as if it succeeded.
returnValue.set(true);
return;
}
p.changeCancelToClose();
try {
final AdtConsoleSdkLog logger = new AdtConsoleSdkLog();
String command[] = new String[] {
androidBat.getAbsolutePath(),
"sdk" //$NON-NLS-1$
};
Process process = Runtime.getRuntime().exec(command);
GrabProcessOutput.grabProcessOutput(
process,
Wait.ASYNC,
new IProcessOutput() {
@Override
public void out(@Nullable String line) {
// Ignore stdout
}
@Override
public void err(@Nullable String line) {
if (line != null) {
logger.info("[SDK Manager] %s", line);
}
}
});
// Set openExternalSdkManager to return true.
returnValue.set(true);
} catch (Exception ignore) {
}
// This small wait prevents the progress dialog from closing too fast.
for (int i = 0; i < numIter; i++) {
if (monitor.isCanceled()) {
// Canceled by user; return true as if it succeeded.
returnValue.set(true);
return;
}
if (i == 10) {
monitor.subTask("Initializing... SDK Manager will show up shortly.");
}
try {
Thread.sleep(sleepMs);
monitor.worked(1);
} catch (InterruptedException e) {
// ignore
}
}
monitor.done();
}
});
} catch (Exception e) {
AdtPlugin.log(e, "SDK Manager exec failed"); //$NON-NLS-1#
return false;
}
return returnValue.get();
}
/**
* Opens the SDK Manager bundled within ADT.
* The call is blocking and does not return till the SD Manager window is closed.
*
* @return True if the SDK location is known and the SDK Manager was started.
* False if the SDK location is not set and we can't open a SDK Manager to
* manage files in an unknown location.
*/
public static boolean openAdtSdkManager() {
final Sdk sdk = Sdk.getCurrent();
if (sdk == null) {
return false;
}
// Runs the updater window, directing only warning/errors logs to the ADT console
// (normal log is just dropped, which is fine since the SDK Manager has its own
// log window now.)
SdkUpdaterWindow window = new SdkUpdaterWindow(
AdtPlugin.getShell(),
new AdtConsoleSdkLog() {
@Override
public void info(@NonNull String msgFormat, Object... args) {
// Do not show non-error/warning log in Eclipse.
};
@Override
public void verbose(@NonNull String msgFormat, Object... args) {
// Do not show non-error/warning log in Eclipse.
};
},
sdk.getSdkOsLocation(),
SdkInvocationContext.IDE);
ISdkChangeListener listener = new ISdkChangeListener() {
@Override
public void onSdkLoaded() {
// Ignore initial load of the SDK.
}
/**
* Unload all we can from the SDK before new packages are installed.
* Typically we need to get rid of references to dx from platform-tools
* and to any platform resource data.
* <p/>
* {@inheritDoc}
*/
@Override
public void preInstallHook() {
// TODO we need to unload as much of as SDK as possible. Otherwise
// on Windows we end up with Eclipse locking some files and we can't
// replace them.
//
// At this point, we know what the user wants to install so it would be
// possible to pass in flags to know what needs to be unloaded. Typically
// we need to:
// - unload dex if platform-tools is going to be updated. There's a vague
// attempt below at removing any references to dex and GCing. Seems
// to do the trick.
// - unload any target that is going to be updated since it may have
// resource data used by a current layout editor (e.g. data/*.ttf
// and various data/res/*.xml).
//
// Most important we need to make sure there isn't a build going on
// and if there is one, either abort it or wait for it to complete and
// then we want to make sure we don't get any attempt to use the SDK
// before the postInstallHook is called.
if (sdk != null) {
sdk.unloadTargetData(true /*preventReload*/);
sdk.unloadDexWrappers();
}
}
/**
* Nothing to do. We'll reparse the SDK later in onSdkReload.
* <p/>
* {@inheritDoc}
*/
@Override
public void postInstallHook() {
}
/**
* Reparse the SDK in case anything was add/removed.
* <p/>
* {@inheritDoc}
*/
@Override
public void onSdkReload() {
AdtPlugin.getDefault().reparseSdk();
}
};
window.addListener(listener);
window.open();
return true;
}
@Override
public void selectionChanged(IAction action, ISelection selection) {
// nothing related to the current selection.
}
@Override
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
// nothing to do.
}
}