Home | History | Annotate | Download | only in actions
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.actions;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.annotations.NonNull;
     21 import com.android.annotations.Nullable;
     22 import com.android.ide.eclipse.adt.AdtPlugin;
     23 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     24 import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
     25 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     26 import com.android.sdklib.io.FileOp;
     27 import com.android.sdklib.repository.ISdkChangeListener;
     28 import com.android.utils.GrabProcessOutput;
     29 import com.android.utils.GrabProcessOutput.IProcessOutput;
     30 import com.android.utils.GrabProcessOutput.Wait;
     31 import com.android.sdkuilib.repository.SdkUpdaterWindow;
     32 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
     33 
     34 import org.eclipse.core.runtime.IProgressMonitor;
     35 import org.eclipse.jface.action.IAction;
     36 import org.eclipse.jface.dialogs.IDialogConstants;
     37 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
     38 import org.eclipse.jface.operation.IRunnableWithProgress;
     39 import org.eclipse.jface.viewers.ISelection;
     40 import org.eclipse.swt.widgets.Display;
     41 import org.eclipse.swt.widgets.Shell;
     42 import org.eclipse.ui.IObjectActionDelegate;
     43 import org.eclipse.ui.IWorkbenchPart;
     44 import org.eclipse.ui.IWorkbenchWindow;
     45 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
     46 
     47 import java.io.File;
     48 import java.lang.reflect.InvocationTargetException;
     49 import java.util.concurrent.atomic.AtomicBoolean;
     50 
     51 /**
     52  * Delegate for the toolbar/menu action "Android SDK Manager".
     53  * It displays the Android SDK Manager.
     54  */
     55 public class SdkManagerAction implements IWorkbenchWindowActionDelegate, IObjectActionDelegate {
     56 
     57     @Override
     58     public void dispose() {
     59         // nothing to dispose.
     60     }
     61 
     62     @Override
     63     public void init(IWorkbenchWindow window) {
     64         // no init
     65     }
     66 
     67     @Override
     68     public void run(IAction action) {
     69         // Although orthogonal to the sdk manager action, this is a good time
     70         // to check whether the SDK has changed on disk.
     71         AdtPlugin.getDefault().refreshSdk();
     72 
     73         if (!openExternalSdkManager()) {
     74             // If we failed to execute the sdk manager, check the SDK location.
     75             // If it's not properly set, the check will display a dialog to state
     76             // so to the user and a link to the prefs.
     77             // Here's it's ok to call checkSdkLocationAndId() since it will not try
     78             // to run the SdkManagerAction (it might run openExternalSdkManager though.)
     79             // If checkSdkLocationAndId tries to open the SDK Manager, it end up using
     80             // the internal one.
     81             if (AdtPlugin.getDefault().checkSdkLocationAndId()) {
     82                 // The SDK check was successful, yet the sdk manager fail to launch anyway.
     83                 AdtPlugin.displayError(
     84                         "Android SDK",
     85                         "Failed to run the Android SDK Manager. Check the Android Console View for details.");
     86             }
     87         }
     88     }
     89 
     90     /**
     91      * A custom implementation of {@link ProgressMonitorDialog} that allows us
     92      * to rename the "Cancel" button to "Close" from the internal task.
     93      */
     94     private static class CloseableProgressMonitorDialog extends ProgressMonitorDialog {
     95 
     96         public CloseableProgressMonitorDialog(Shell parent) {
     97             super(parent);
     98         }
     99 
    100         public void changeCancelToClose() {
    101             if (cancel != null && !cancel.isDisposed()) {
    102                 Display display = getShell() == null ? null : getShell().getDisplay();
    103                 if (display != null) {
    104                     display.syncExec(new Runnable() {
    105                         @Override
    106                         public void run() {
    107                             if (cancel != null && !cancel.isDisposed()) {
    108                                 cancel.setText(IDialogConstants.CLOSE_LABEL);
    109                             }
    110                         }
    111                     });
    112                 }
    113             }
    114         }
    115     }
    116 
    117     /**
    118      * Opens the SDK Manager as an external application.
    119      * This call is asynchronous, it doesn't wait for the manager to be closed.
    120      * <p/>
    121      * Important: this method must NOT invoke {@link AdtPlugin#checkSdkLocationAndId}
    122      * (in any of its variations) since the dialog uses this method to invoke the sdk
    123      * manager if needed.
    124      *
    125      * @return True if the application was found and executed. False if it could not
    126      *   be located or could not be launched.
    127      */
    128     public static boolean openExternalSdkManager() {
    129 
    130         // On windows this takes a couple seconds and it's not clear the launch action
    131         // has been invoked. To prevent the user from randomly clicking the "open sdk manager"
    132         // button multiple times, show a progress window that will automatically close
    133         // after a couple seconds.
    134 
    135         // By default openExternalSdkManager will return false.
    136         final AtomicBoolean returnValue = new AtomicBoolean(false);
    137 
    138         final CloseableProgressMonitorDialog p =
    139             new CloseableProgressMonitorDialog(AdtPlugin.getShell());
    140         p.setOpenOnRun(true);
    141         try {
    142             p.run(true /*fork*/, true /*cancelable*/, new IRunnableWithProgress() {
    143                 @Override
    144                 public void run(IProgressMonitor monitor)
    145                         throws InvocationTargetException, InterruptedException {
    146 
    147                     // Get the SDK locatiom from the current SDK or as fallback
    148                     // directly from the ADT preferences.
    149                     Sdk sdk = Sdk.getCurrent();
    150                     String osSdkLocation = sdk == null ? null : sdk.getSdkOsLocation();
    151                     if (osSdkLocation == null || !new File(osSdkLocation).isDirectory()) {
    152                         osSdkLocation = AdtPrefs.getPrefs().getOsSdkFolder();
    153                     }
    154 
    155                     // If there's no SDK location or it's not a valid directory,
    156                     // there's nothing we can do. When this is invoked from run()
    157                     // the checkSdkLocationAndId method call should display a dialog
    158                     // telling the user to configure the preferences.
    159                     if (osSdkLocation == null || !new File(osSdkLocation).isDirectory()) {
    160                         return;
    161                     }
    162 
    163                     final int numIter = 30;  //30*100=3s to wait for window
    164                     final int sleepMs = 100;
    165                     monitor.beginTask("Starting Android SDK Manager", numIter);
    166 
    167                     File androidBat = FileOp.append(
    168                             osSdkLocation,
    169                             SdkConstants.FD_TOOLS,
    170                             SdkConstants.androidCmdName());
    171 
    172                     if (!androidBat.exists()) {
    173                         AdtPlugin.printErrorToConsole("SDK Manager",
    174                                 "Missing %s file in Android SDK.", SdkConstants.androidCmdName());
    175                         return;
    176                     }
    177 
    178                     if (monitor.isCanceled()) {
    179                         // Canceled by user; return true as if it succeeded.
    180                         returnValue.set(true);
    181                         return;
    182                     }
    183 
    184                     p.changeCancelToClose();
    185 
    186                     try {
    187                         final AdtConsoleSdkLog logger = new AdtConsoleSdkLog();
    188 
    189                         String command[] = new String[] {
    190                                 androidBat.getAbsolutePath(),
    191                                 "sdk"   //$NON-NLS-1$
    192                         };
    193                         Process process = Runtime.getRuntime().exec(command);
    194                         GrabProcessOutput.grabProcessOutput(
    195                                 process,
    196                                 Wait.ASYNC,
    197                                 new IProcessOutput() {
    198                                     @Override
    199                                     public void out(@Nullable String line) {
    200                                         // Ignore stdout
    201                                     }
    202 
    203                                     @Override
    204                                     public void err(@Nullable String line) {
    205                                         if (line != null) {
    206                                             logger.info("[SDK Manager] %s", line);
    207                                         }
    208                                     }
    209                                 });
    210 
    211                         // Set openExternalSdkManager to return true.
    212                         returnValue.set(true);
    213                     } catch (Exception ignore) {
    214                     }
    215 
    216                     // This small wait prevents the progress dialog from closing too fast.
    217                     for (int i = 0; i < numIter; i++) {
    218                         if (monitor.isCanceled()) {
    219                             // Canceled by user; return true as if it succeeded.
    220                             returnValue.set(true);
    221                             return;
    222                         }
    223                         if (i == 10) {
    224                             monitor.subTask("Initializing... SDK Manager will show up shortly.");
    225                         }
    226                         try {
    227                             Thread.sleep(sleepMs);
    228                             monitor.worked(1);
    229                         } catch (InterruptedException e) {
    230                             // ignore
    231                         }
    232                     }
    233 
    234                     monitor.done();
    235                 }
    236             });
    237         } catch (Exception e) {
    238             AdtPlugin.log(e, "SDK Manager exec failed");    //$NON-NLS-1#
    239             return false;
    240         }
    241 
    242         return returnValue.get();
    243     }
    244 
    245     /**
    246      * Opens the SDK Manager bundled within ADT.
    247      * The call is blocking and does not return till the SD Manager window is closed.
    248      *
    249      * @return True if the SDK location is known and the SDK Manager was started.
    250      *   False if the SDK location is not set and we can't open a SDK Manager to
    251      *   manage files in an unknown location.
    252      */
    253     public static boolean openAdtSdkManager() {
    254         final Sdk sdk = Sdk.getCurrent();
    255         if (sdk == null) {
    256             return false;
    257         }
    258 
    259         // Runs the updater window, directing only warning/errors logs to the ADT console
    260         // (normal log is just dropped, which is fine since the SDK Manager has its own
    261         // log window now.)
    262 
    263         SdkUpdaterWindow window = new SdkUpdaterWindow(
    264                 AdtPlugin.getShell(),
    265                 new AdtConsoleSdkLog() {
    266                     @Override
    267                     public void info(@NonNull String msgFormat, Object... args) {
    268                         // Do not show non-error/warning log in Eclipse.
    269                     };
    270                     @Override
    271                     public void verbose(@NonNull String msgFormat, Object... args) {
    272                         // Do not show non-error/warning log in Eclipse.
    273                     };
    274                 },
    275                 sdk.getSdkOsLocation(),
    276                 SdkInvocationContext.IDE);
    277 
    278         ISdkChangeListener listener = new ISdkChangeListener() {
    279             @Override
    280             public void onSdkLoaded() {
    281                 // Ignore initial load of the SDK.
    282             }
    283 
    284             /**
    285              * Unload all we can from the SDK before new packages are installed.
    286              * Typically we need to get rid of references to dx from platform-tools
    287              * and to any platform resource data.
    288              * <p/>
    289              * {@inheritDoc}
    290              */
    291             @Override
    292             public void preInstallHook() {
    293 
    294                 // TODO we need to unload as much of as SDK as possible. Otherwise
    295                 // on Windows we end up with Eclipse locking some files and we can't
    296                 // replace them.
    297                 //
    298                 // At this point, we know what the user wants to install so it would be
    299                 // possible to pass in flags to know what needs to be unloaded. Typically
    300                 // we need to:
    301                 // - unload dex if platform-tools is going to be updated. There's a vague
    302                 //   attempt below at removing any references to dex and GCing. Seems
    303                 //   to do the trick.
    304                 // - unload any target that is going to be updated since it may have
    305                 //   resource data used by a current layout editor (e.g. data/*.ttf
    306                 //   and various data/res/*.xml).
    307                 //
    308                 // Most important we need to make sure there isn't a build going on
    309                 // and if there is one, either abort it or wait for it to complete and
    310                 // then we want to make sure we don't get any attempt to use the SDK
    311                 // before the postInstallHook is called.
    312 
    313                 if (sdk != null) {
    314                     sdk.unloadTargetData(true /*preventReload*/);
    315                     sdk.unloadDexWrappers();
    316                 }
    317             }
    318 
    319             /**
    320              * Nothing to do. We'll reparse the SDK later in onSdkReload.
    321              * <p/>
    322              * {@inheritDoc}
    323              */
    324             @Override
    325             public void postInstallHook() {
    326             }
    327 
    328             /**
    329              * Reparse the SDK in case anything was add/removed.
    330              * <p/>
    331              * {@inheritDoc}
    332              */
    333             @Override
    334             public void onSdkReload() {
    335                 AdtPlugin.getDefault().reparseSdk();
    336             }
    337         };
    338 
    339         window.addListener(listener);
    340         window.open();
    341 
    342         return true;
    343     }
    344 
    345     @Override
    346     public void selectionChanged(IAction action, ISelection selection) {
    347         // nothing related to the current selection.
    348     }
    349 
    350     @Override
    351     public void setActivePart(IAction action, IWorkbenchPart targetPart) {
    352         // nothing to do.
    353     }
    354 }
    355