OSDN Git Service

SDK Updater, restart ADB after updating tool/addon package.
authorRaphael <raphael@google.com>
Sun, 16 Aug 2009 20:07:30 +0000 (13:07 -0700)
committerRaphael <raphael@google.com>
Mon, 17 Aug 2009 19:10:30 +0000 (12:10 -0700)
There's a new setting for this too.

BUG 1922590

sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java [new file with mode: 0755]
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java

index 8ab1364..c800054 100755 (executable)
@@ -48,6 +48,7 @@ public class SettingsPage extends Composite implements ISettingsPage {
     private Text mProxyServerText;\r
     private Text mProxyPortText;\r
     private Button mForceHttpCheck;\r
+    private Button mAskAdbRestartCheck;\r
 \r
     private ModifyListener mSetApplyDirty = new ModifyListener() {\r
         public void modifyText(ModifyEvent e) {\r
@@ -73,18 +74,26 @@ public class SettingsPage extends Composite implements ISettingsPage {
         mProxyServerLabel = new Label(mProxySettingsGroup, SWT.NONE);\r
         mProxyServerLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));\r
         mProxyServerLabel.setText("HTTP Proxy Server");\r
+        String tooltip = "The DNS name or IP of the HTTP proxy server to use. " +\r
+                         "When empty, no HTTP proxy is used.";\r
+        mProxyServerLabel.setToolTipText(tooltip);\r
 \r
         mProxyServerText = new Text(mProxySettingsGroup, SWT.BORDER);\r
         mProxyServerText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));\r
         mProxyServerText.addModifyListener(mSetApplyDirty);\r
+        mProxyServerText.setToolTipText(tooltip);\r
 \r
         mProxyPortLabel = new Label(mProxySettingsGroup, SWT.NONE);\r
         mProxyPortLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));\r
         mProxyPortLabel.setText("HTTP Proxy Port");\r
+        tooltip = "The port of the HTTP proxy server to use. " +\r
+                  "When empty, the default for HTTP or HTTPS is used.";\r
+        mProxyPortLabel.setToolTipText(tooltip);\r
 \r
         mProxyPortText = new Text(mProxySettingsGroup, SWT.BORDER);\r
         mProxyPortText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));\r
         mProxyPortText.addModifyListener(mSetApplyDirty);\r
+        mProxyPortText.setToolTipText(tooltip);\r
 \r
         mMiscGroup = new Group(this, SWT.NONE);\r
         mMiscGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));\r
@@ -92,7 +101,10 @@ public class SettingsPage extends Composite implements ISettingsPage {
         mMiscGroup.setLayout(new GridLayout(2, false));\r
 \r
         mForceHttpCheck = new Button(mMiscGroup, SWT.CHECK);\r
+        mForceHttpCheck.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));\r
         mForceHttpCheck.setText("Force https://... sources to be fetched using http://...");\r
+        mForceHttpCheck.setToolTipText("If you are not able to connect to the official Android repository " +\r
+                "using HTTPS, enable this setting to force accessing it via HTTP.");\r
         mForceHttpCheck.addSelectionListener(new SelectionAdapter() {\r
             @Override\r
             public void widgetSelected(SelectionEvent e) {\r
@@ -100,6 +112,18 @@ public class SettingsPage extends Composite implements ISettingsPage {
             }\r
         });\r
 \r
+        mAskAdbRestartCheck = new Button(mMiscGroup, SWT.CHECK);\r
+        mAskAdbRestartCheck.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));\r
+        mAskAdbRestartCheck.setText("Ask before restarting ADB");\r
+        mAskAdbRestartCheck.setToolTipText("When checked, the user will be asked for permission " +\r
+                "to restart ADB after updating an addon-on package or a tool package.");\r
+        mAskAdbRestartCheck.addSelectionListener(new SelectionAdapter() {\r
+            @Override\r
+            public void widgetSelected(SelectionEvent e) {\r
+                onForceHttpSelected();  //$hide$\r
+            }\r
+        });\r
+\r
         mApplyButton = new Button(this, SWT.NONE);\r
         mApplyButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));\r
         mApplyButton.setText("Save && Apply");\r
@@ -139,6 +163,7 @@ public class SettingsPage extends Composite implements ISettingsPage {
         mProxyServerText.setText(in_settings.getProperty(KEY_HTTP_PROXY_HOST, ""));  //$NON-NLS-1$\r
         mProxyPortText.setText(  in_settings.getProperty(KEY_HTTP_PROXY_PORT, ""));  //$NON-NLS-1$\r
         mForceHttpCheck.setSelection(Boolean.parseBoolean(in_settings.getProperty(KEY_FORCE_HTTP)));\r
+        mAskAdbRestartCheck.setSelection(Boolean.parseBoolean(in_settings.getProperty(KEY_ASK_ADB_RESTART)));\r
 \r
         // We loaded fresh settings so there's nothing dirty to apply\r
         mApplyButton.setEnabled(false);\r
@@ -151,6 +176,8 @@ public class SettingsPage extends Composite implements ISettingsPage {
         out_settings.setProperty(KEY_HTTP_PROXY_PORT, mProxyPortText.getText());\r
         out_settings.setProperty(KEY_FORCE_HTTP,\r
                 Boolean.toString(mForceHttpCheck.getSelection()));\r
+        out_settings.setProperty(KEY_ASK_ADB_RESTART,\r
+                Boolean.toString(mAskAdbRestartCheck.getSelection()));\r
     }\r
 \r
     /**\r
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java
new file mode 100755 (executable)
index 0000000..cb553d1
--- /dev/null
@@ -0,0 +1,221 @@
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.sdkuilib.internal.repository;\r
+\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.internal.repository.ITaskMonitor;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+import java.util.ArrayList;\r
+\r
+/**\r
+ * A lightweight wrapper to start & stop ADB.\r
+ */\r
+public class AdbWrapper {\r
+\r
+    /*\r
+     * Note: we could bring ddmlib in SdkManager for that purpose, however this allows us to\r
+     * specialize the start/stop methods to our needs (e.g. a task monitor, etc.)\r
+     */\r
+\r
+    private final String mAdbOsLocation;\r
+    private final ITaskMonitor mMonitor;\r
+\r
+    /**\r
+     * Creates a new lightweight ADB wrapper.\r
+     *\r
+     * @param osSdkPath The root OS path of the SDK. Cannot be null.\r
+     * @param monitor A logger object. Cannot be null.\r
+     */\r
+    public AdbWrapper(String osSdkPath, ITaskMonitor monitor) {\r
+        mMonitor = monitor;\r
+\r
+        if (!osSdkPath.endsWith(File.separator)) {\r
+            osSdkPath += File.separator;\r
+        }\r
+        mAdbOsLocation = osSdkPath + SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ADB;\r
+    }\r
+\r
+    private void display(String format, Object...args) {\r
+        mMonitor.setResult(format, args);\r
+    }\r
+\r
+    /**\r
+     * Starts the adb host side server.\r
+     * @return true if success\r
+     */\r
+    public synchronized boolean startAdb() {\r
+        if (mAdbOsLocation == null) {\r
+            display("Error: missing path to ADB."); //$NON-NLS-1$\r
+            return false;\r
+        }\r
+\r
+        Process proc;\r
+        int status = -1;\r
+\r
+        try {\r
+            String[] command = new String[2];\r
+            command[0] = mAdbOsLocation;\r
+            command[1] = "start-server"; //$NON-NLS-1$\r
+            proc = Runtime.getRuntime().exec(command);\r
+\r
+            ArrayList<String> errorOutput = new ArrayList<String>();\r
+            ArrayList<String> stdOutput = new ArrayList<String>();\r
+            status = grabProcessOutput(proc, errorOutput, stdOutput,\r
+                    false /* waitForReaders */);\r
+\r
+        } catch (IOException ioe) {\r
+            display("Unable to run 'adb': %1$s.", ioe.getMessage()); //$NON-NLS-1$\r
+            // we'll return false;\r
+        } catch (InterruptedException ie) {\r
+            display("Unable to run 'adb': %1$s.", ie.getMessage()); //$NON-NLS-1$\r
+            // we'll return false;\r
+        }\r
+\r
+        if (status != 0) {\r
+            display("'adb start-server' failed."); //$NON-NLS-1$\r
+            return false;\r
+        }\r
+\r
+        display("'adb start-server' succeeded."); //$NON-NLS-1$\r
+\r
+        return true;\r
+    }\r
+\r
+    /**\r
+     * Stops the adb host side server.\r
+     * @return true if success\r
+     */\r
+    public synchronized boolean stopAdb() {\r
+        if (mAdbOsLocation == null) {\r
+            display("Error: missing path to ADB."); //$NON-NLS-1$\r
+            return false;\r
+        }\r
+\r
+        Process proc;\r
+        int status = -1;\r
+\r
+        try {\r
+            String[] command = new String[2];\r
+            command[0] = mAdbOsLocation;\r
+            command[1] = "kill-server"; //$NON-NLS-1$\r
+            proc = Runtime.getRuntime().exec(command);\r
+            status = proc.waitFor();\r
+        }\r
+        catch (IOException ioe) {\r
+            // we'll return false;\r
+        }\r
+        catch (InterruptedException ie) {\r
+            // we'll return false;\r
+        }\r
+\r
+        if (status != 0) {\r
+            display("'adb kill-server' failed -- run manually if necessary."); //$NON-NLS-1$\r
+            return false;\r
+        }\r
+\r
+        display("'adb kill-server' succeeded."); //$NON-NLS-1$\r
+        return true;\r
+    }\r
+\r
+    /**\r
+     * Get the stderr/stdout outputs of a process and return when the process is done.\r
+     * Both <b>must</b> be read or the process will block on windows.\r
+     * @param process The process to get the ouput from\r
+     * @param errorOutput The array to store the stderr output. cannot be null.\r
+     * @param stdOutput The array to store the stdout output. cannot be null.\r
+     * @param waitforReaders if true, this will wait for the reader threads.\r
+     * @return the process return code.\r
+     * @throws InterruptedException\r
+     */\r
+    private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,\r
+            final ArrayList<String> stdOutput, boolean waitforReaders)\r
+            throws InterruptedException {\r
+        assert errorOutput != null;\r
+        assert stdOutput != null;\r
+        // read the lines as they come. if null is returned, it's\r
+        // because the process finished\r
+        Thread t1 = new Thread("") { //$NON-NLS-1$\r
+            @Override\r
+            public void run() {\r
+                // create a buffer to read the stderr output\r
+                InputStreamReader is = new InputStreamReader(process.getErrorStream());\r
+                BufferedReader errReader = new BufferedReader(is);\r
+\r
+                try {\r
+                    while (true) {\r
+                        String line = errReader.readLine();\r
+                        if (line != null) {\r
+                            display("ADB Error: %1$s", line);\r
+                            errorOutput.add(line);\r
+                        } else {\r
+                            break;\r
+                        }\r
+                    }\r
+                } catch (IOException e) {\r
+                    // do nothing.\r
+                }\r
+            }\r
+        };\r
+\r
+        Thread t2 = new Thread("") { //$NON-NLS-1$\r
+            @Override\r
+            public void run() {\r
+                InputStreamReader is = new InputStreamReader(process.getInputStream());\r
+                BufferedReader outReader = new BufferedReader(is);\r
+\r
+                try {\r
+                    while (true) {\r
+                        String line = outReader.readLine();\r
+                        if (line != null) {\r
+                            display("ADB: %1$s", line);\r
+                            stdOutput.add(line);\r
+                        } else {\r
+                            break;\r
+                        }\r
+                    }\r
+                } catch (IOException e) {\r
+                    // do nothing.\r
+                }\r
+            }\r
+        };\r
+\r
+        t1.start();\r
+        t2.start();\r
+\r
+        // it looks like on windows process#waitFor() can return\r
+        // before the thread have filled the arrays, so we wait for both threads and the\r
+        // process itself.\r
+        if (waitforReaders) {\r
+            try {\r
+                t1.join();\r
+            } catch (InterruptedException e) {\r
+            }\r
+            try {\r
+                t2.join();\r
+            } catch (InterruptedException e) {\r
+            }\r
+        }\r
+\r
+        // get the return code from the process\r
+        return process.waitFor();\r
+    }\r
+\r
+}\r
index 78b04f3..0d7179c 100755 (executable)
@@ -24,14 +24,34 @@ import java.util.Properties;
  */\r
 public interface ISettingsPage {\r
 \r
-    /** Java system setting picked up by {@link URL} for http proxy port. Type: String. */\r
+    /**\r
+     * Java system setting picked up by {@link URL} for http proxy port.\r
+     * Type: String.\r
+     */\r
     public static final String KEY_HTTP_PROXY_PORT = "http.proxyPort";           //$NON-NLS-1$\r
-    /** Java system setting picked up by {@link URL} for http proxy host. Type: String. */\r
+    /**\r
+     * Java system setting picked up by {@link URL} for http proxy host.\r
+     * Type: String.\r
+     */\r
     public static final String KEY_HTTP_PROXY_HOST = "http.proxyHost";           //$NON-NLS-1$\r
-    /** Setting to force using http:// instead of https:// connections. Type: Boolean. */\r
+    /**\r
+     * Setting to force using http:// instead of https:// connections.\r
+     * Type: Boolean.\r
+     * Default: False.\r
+     */\r
     public static final String KEY_FORCE_HTTP = "sdkman.force.http";             //$NON-NLS-1$\r
-    /** Setting to display only packages that are new or updates. Type: Boolean. */\r
+    /**\r
+     * Setting to display only packages that are new or updates.\r
+     * Type: Boolean.\r
+     * Default: True.\r
+     */\r
     public static final String KEY_SHOW_UPDATE_ONLY = "sdkman.show.update.only"; //$NON-NLS-1$\r
+    /**\r
+     * Setting to ask for permission before restarting ADB.\r
+     * Type: Boolean.\r
+     * Default: True.\r
+     */\r
+    public static final String KEY_ASK_ADB_RESTART = "sdkman.ask.adb.restart";   //$NON-NLS-1$\r
 \r
     /** Loads settings from the given {@link Properties} container and update the page UI. */\r
     public abstract void loadSettings(Properties in_settings);\r
index 8f5c4d2..1898f66 100755 (executable)
@@ -38,13 +38,11 @@ import org.eclipse.swt.events.ControlAdapter;
 import org.eclipse.swt.events.ControlEvent;\r
 import org.eclipse.swt.events.SelectionAdapter;\r
 import org.eclipse.swt.events.SelectionEvent;\r
-import org.eclipse.swt.graphics.Color;\r
 import org.eclipse.swt.graphics.Rectangle;\r
 import org.eclipse.swt.layout.GridData;\r
 import org.eclipse.swt.layout.GridLayout;\r
 import org.eclipse.swt.widgets.Button;\r
 import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Display;\r
 import org.eclipse.swt.widgets.Group;\r
 import org.eclipse.swt.widgets.Label;\r
 import org.eclipse.swt.widgets.Tree;\r
index b6363d3..a4b1a05 100755 (executable)
@@ -26,7 +26,11 @@ import java.io.IOException;
 import java.util.Properties;\r
 \r
 /**\r
- *\r
+ * Controller class to get settings values. Settings are kept in-memory.\r
+ * Users of this class must first load the settings before changing them and save\r
+ * them when modified.\r
+ * <p/>\r
+ * Settings are enumerated by constants in {@link ISettingsPage}.\r
  */\r
 public class SettingsController {\r
 \r
@@ -41,10 +45,30 @@ public class SettingsController {
 \r
     //--- Access to settings ------------\r
 \r
+    /**\r
+     * Returns the value of the ISettingsPage#KEY_FORCE_HTTP setting.\r
+     * @see ISettingsPage#KEY_FORCE_HTTP\r
+     */\r
     public boolean getForceHttp() {\r
         return Boolean.parseBoolean(mProperties.getProperty(ISettingsPage.KEY_FORCE_HTTP));\r
     }\r
 \r
+    /**\r
+     * Returns the value of the ISettingsPage#KEY_ASK_ADB_RESTART setting.\r
+     * @see ISettingsPage#KEY_ASK_ADB_RESTART\r
+     */\r
+    public boolean getAskBeforeAdbRestart() {\r
+        String value = mProperties.getProperty(ISettingsPage.KEY_ASK_ADB_RESTART);\r
+        if (value == null) {\r
+            return true;\r
+        }\r
+        return Boolean.parseBoolean(value);\r
+    }\r
+\r
+    /**\r
+     * Returns the value of the ISettingsPage#KEY_SHOW_UPDATE_ONLY setting.\r
+     * @see ISettingsPage#KEY_SHOW_UPDATE_ONLY\r
+     */\r
     public boolean getShowUpdateOnly() {\r
         String value = mProperties.getProperty(ISettingsPage.KEY_SHOW_UPDATE_ONLY);\r
         if (value == null) {\r
@@ -53,8 +77,20 @@ public class SettingsController {
         return Boolean.parseBoolean(value);\r
     }\r
 \r
+    /**\r
+     * Sets the value of the ISettingsPage#KEY_SHOW_UPDATE_ONLY setting.\r
+     * @param enabled True if only compatible update items should be shown.\r
+     * @see ISettingsPage#KEY_SHOW_UPDATE_ONLY\r
+     */\r
     public void setShowUpdateOnly(boolean enabled) {\r
-        mProperties.setProperty(ISettingsPage.KEY_SHOW_UPDATE_ONLY, Boolean.toString(enabled));\r
+        setSetting(ISettingsPage.KEY_SHOW_UPDATE_ONLY, enabled);\r
+    }\r
+\r
+    /**\r
+     * Internal helper to set a boolean setting.\r
+     */\r
+    private void setSetting(String key, boolean value) {\r
+        mProperties.setProperty(key, Boolean.toString(value));\r
     }\r
 \r
     //--- Controller methods -------------\r
@@ -89,6 +125,10 @@ public class SettingsController {
                 fis = new FileInputStream(f);\r
 \r
                 mProperties.load(fis);\r
+\r
+                // Properly reformat some settings to enforce their default value when missing.\r
+                setShowUpdateOnly(getShowUpdateOnly());\r
+                setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, getAskBeforeAdbRestart());\r
             }\r
 \r
         } catch (AndroidLocationException e) {\r
index a605e3d..bec00f8 100755 (executable)
@@ -34,6 +34,8 @@ import com.android.sdklib.internal.repository.Package.UpdateInfo;
 import com.android.sdkuilib.internal.repository.icons.ImageFactory;\r
 import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;\r
 \r
+import org.eclipse.jface.dialogs.MessageDialog;\r
+import org.eclipse.swt.widgets.Display;\r
 import org.eclipse.swt.widgets.Shell;\r
 \r
 import java.io.ByteArrayOutputStream;\r
@@ -294,14 +296,15 @@ class UpdaterData {
                             break;\r
                         }\r
 \r
-                        if (archive.getParentPackage() instanceof AddonPackage) {\r
-                            installedAddon = true;\r
-                        } else if (archive.getParentPackage() instanceof ToolPackage) {\r
-                            installedTools = true;\r
-                        }\r
-\r
                         if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {\r
                             numInstalled++;\r
+\r
+                            // Check if we successfully installed a tool or add-on package.\r
+                            if (archive.getParentPackage() instanceof AddonPackage) {\r
+                                installedAddon = true;\r
+                            } else if (archive.getParentPackage() instanceof ToolPackage) {\r
+                                installedTools = true;\r
+                            }\r
                         }\r
 \r
                     } catch (Throwable t) {\r
@@ -334,8 +337,10 @@ class UpdaterData {
                     // Update the USB vendor ids for adb\r
                     try {\r
                         mSdkManager.updateAdb();\r
+                        monitor.setResult("Updated ADB to support the USB devices declared in the SDK add-ons.");\r
                     } catch (Exception e) {\r
                         mSdkLog.error(e, "Update ADB failed");\r
+                        monitor.setResult("failed to update adb to support the USB devices declared in the SDK add-ons.");\r
                     }\r
                 }\r
 \r
@@ -346,8 +351,11 @@ class UpdaterData {
                     // before updating the tools folder, as adb.exe is (surprisingly) not\r
                     // locked.\r
 \r
-                    // TODO either bring in ddmlib and use its existing methods to stop adb\r
-                    // or use a shell exec to tools/adb.\r
+                    askForAdbRestart(monitor);\r
+                }\r
+\r
+                if (installedTools) {\r
+                    notifyToolsNeedsToBeRestarted();\r
                 }\r
 \r
                 if (numInstalled == 0) {\r
@@ -365,6 +373,54 @@ class UpdaterData {
     }\r
 \r
     /**\r
+     * Attemps to restart ADB.\r
+     *\r
+     * If the "ask before restart" setting is set (the default), prompt the user whether\r
+     * now is a good time to restart ADB.\r
+     * @param monitor\r
+     */\r
+    private void askForAdbRestart(ITaskMonitor monitor) {\r
+        final boolean[] canRestart = new boolean[] { true };\r
+\r
+        if (getSettingsController().getAskBeforeAdbRestart()) {\r
+            // need to ask for permission first\r
+            Display display = mWindowShell.getDisplay();\r
+\r
+            display.syncExec(new Runnable() {\r
+                public void run() {\r
+                    canRestart[0] = MessageDialog.openQuestion(mWindowShell,\r
+                            "ADB Restart",\r
+                            "A package that depends on ADB has been updated. It is recommended " +\r
+                            "to restart ADB. Is it OK to do it now? If not, you can restart it " +\r
+                            "manually later.");\r
+                }\r
+            });\r
+        }\r
+\r
+        if (canRestart[0]) {\r
+            AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor);\r
+            adb.stopAdb();\r
+            adb.startAdb();\r
+        }\r
+    }\r
+\r
+    private void notifyToolsNeedsToBeRestarted() {\r
+        Display display = mWindowShell.getDisplay();\r
+\r
+        display.syncExec(new Runnable() {\r
+            public void run() {\r
+                MessageDialog.openInformation(mWindowShell,\r
+                        "Android Tools Updated",\r
+                        "The Android SDK tool that you are currently using has been updated. " +\r
+                        "It is recommended that you now close the Android SDK window and re-open it. " +\r
+                        "If you started this window from Eclipse, please check if the Android " +\r
+                        "plug-in needs to be updated.");\r
+            }\r
+        });\r
+    }\r
+\r
+\r
+    /**\r
      * Tries to update all the *existing* local packages.\r
      * This first refreshes all sources, then compares the available remote packages with\r
      * the current local ones and suggest updates to be done to the user. Finally all\r