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