diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java new file mode 100644 index 000000000..48667bea0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java @@ -0,0 +1,354 @@ +/* + * 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. + } +} |