diff --git a/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java b/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java index 8ab1364c8..c80005447 100755 --- a/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java +++ b/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java @@ -48,6 +48,7 @@ public class SettingsPage extends Composite implements ISettingsPage { private Text mProxyServerText; private Text mProxyPortText; private Button mForceHttpCheck; + private Button mAskAdbRestartCheck; private ModifyListener mSetApplyDirty = new ModifyListener() { public void modifyText(ModifyEvent e) { @@ -73,18 +74,26 @@ public class SettingsPage extends Composite implements ISettingsPage { mProxyServerLabel = new Label(mProxySettingsGroup, SWT.NONE); mProxyServerLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); mProxyServerLabel.setText("HTTP Proxy Server"); + String tooltip = "The DNS name or IP of the HTTP proxy server to use. " + + "When empty, no HTTP proxy is used."; + mProxyServerLabel.setToolTipText(tooltip); mProxyServerText = new Text(mProxySettingsGroup, SWT.BORDER); mProxyServerText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); mProxyServerText.addModifyListener(mSetApplyDirty); + mProxyServerText.setToolTipText(tooltip); mProxyPortLabel = new Label(mProxySettingsGroup, SWT.NONE); mProxyPortLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); mProxyPortLabel.setText("HTTP Proxy Port"); + tooltip = "The port of the HTTP proxy server to use. " + + "When empty, the default for HTTP or HTTPS is used."; + mProxyPortLabel.setToolTipText(tooltip); mProxyPortText = new Text(mProxySettingsGroup, SWT.BORDER); mProxyPortText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); mProxyPortText.addModifyListener(mSetApplyDirty); + mProxyPortText.setToolTipText(tooltip); mMiscGroup = new Group(this, SWT.NONE); mMiscGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); @@ -92,7 +101,10 @@ public class SettingsPage extends Composite implements ISettingsPage { mMiscGroup.setLayout(new GridLayout(2, false)); mForceHttpCheck = new Button(mMiscGroup, SWT.CHECK); + mForceHttpCheck.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); mForceHttpCheck.setText("Force https://... sources to be fetched using http://..."); + mForceHttpCheck.setToolTipText("If you are not able to connect to the official Android repository " + + "using HTTPS, enable this setting to force accessing it via HTTP."); mForceHttpCheck.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { @@ -100,6 +112,18 @@ public class SettingsPage extends Composite implements ISettingsPage { } }); + mAskAdbRestartCheck = new Button(mMiscGroup, SWT.CHECK); + mAskAdbRestartCheck.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); + mAskAdbRestartCheck.setText("Ask before restarting ADB"); + mAskAdbRestartCheck.setToolTipText("When checked, the user will be asked for permission " + + "to restart ADB after updating an addon-on package or a tool package."); + mAskAdbRestartCheck.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onForceHttpSelected(); //$hide$ + } + }); + mApplyButton = new Button(this, SWT.NONE); mApplyButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); mApplyButton.setText("Save && Apply"); @@ -139,6 +163,7 @@ public class SettingsPage extends Composite implements ISettingsPage { mProxyServerText.setText(in_settings.getProperty(KEY_HTTP_PROXY_HOST, "")); //$NON-NLS-1$ mProxyPortText.setText( in_settings.getProperty(KEY_HTTP_PROXY_PORT, "")); //$NON-NLS-1$ mForceHttpCheck.setSelection(Boolean.parseBoolean(in_settings.getProperty(KEY_FORCE_HTTP))); + mAskAdbRestartCheck.setSelection(Boolean.parseBoolean(in_settings.getProperty(KEY_ASK_ADB_RESTART))); // We loaded fresh settings so there's nothing dirty to apply mApplyButton.setEnabled(false); @@ -151,6 +176,8 @@ public class SettingsPage extends Composite implements ISettingsPage { out_settings.setProperty(KEY_HTTP_PROXY_PORT, mProxyPortText.getText()); out_settings.setProperty(KEY_FORCE_HTTP, Boolean.toString(mForceHttpCheck.getSelection())); + out_settings.setProperty(KEY_ASK_ADB_RESTART, + Boolean.toString(mAskAdbRestartCheck.getSelection())); } /** diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java new file mode 100755 index 000000000..cb553d119 --- /dev/null +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.sdkuilib.internal.repository; + +import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.repository.ITaskMonitor; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + +/** + * A lightweight wrapper to start & stop ADB. + */ +public class AdbWrapper { + + /* + * Note: we could bring ddmlib in SdkManager for that purpose, however this allows us to + * specialize the start/stop methods to our needs (e.g. a task monitor, etc.) + */ + + private final String mAdbOsLocation; + private final ITaskMonitor mMonitor; + + /** + * Creates a new lightweight ADB wrapper. + * + * @param osSdkPath The root OS path of the SDK. Cannot be null. + * @param monitor A logger object. Cannot be null. + */ + public AdbWrapper(String osSdkPath, ITaskMonitor monitor) { + mMonitor = monitor; + + if (!osSdkPath.endsWith(File.separator)) { + osSdkPath += File.separator; + } + mAdbOsLocation = osSdkPath + SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ADB; + } + + private void display(String format, Object...args) { + mMonitor.setResult(format, args); + } + + /** + * Starts the adb host side server. + * @return true if success + */ + public synchronized boolean startAdb() { + if (mAdbOsLocation == null) { + display("Error: missing path to ADB."); //$NON-NLS-1$ + return false; + } + + Process proc; + int status = -1; + + try { + String[] command = new String[2]; + command[0] = mAdbOsLocation; + command[1] = "start-server"; //$NON-NLS-1$ + proc = Runtime.getRuntime().exec(command); + + ArrayList errorOutput = new ArrayList(); + ArrayList stdOutput = new ArrayList(); + status = grabProcessOutput(proc, errorOutput, stdOutput, + false /* waitForReaders */); + + } catch (IOException ioe) { + display("Unable to run 'adb': %1$s.", ioe.getMessage()); //$NON-NLS-1$ + // we'll return false; + } catch (InterruptedException ie) { + display("Unable to run 'adb': %1$s.", ie.getMessage()); //$NON-NLS-1$ + // we'll return false; + } + + if (status != 0) { + display("'adb start-server' failed."); //$NON-NLS-1$ + return false; + } + + display("'adb start-server' succeeded."); //$NON-NLS-1$ + + return true; + } + + /** + * Stops the adb host side server. + * @return true if success + */ + public synchronized boolean stopAdb() { + if (mAdbOsLocation == null) { + display("Error: missing path to ADB."); //$NON-NLS-1$ + return false; + } + + Process proc; + int status = -1; + + try { + String[] command = new String[2]; + command[0] = mAdbOsLocation; + command[1] = "kill-server"; //$NON-NLS-1$ + proc = Runtime.getRuntime().exec(command); + status = proc.waitFor(); + } + catch (IOException ioe) { + // we'll return false; + } + catch (InterruptedException ie) { + // we'll return false; + } + + if (status != 0) { + display("'adb kill-server' failed -- run manually if necessary."); //$NON-NLS-1$ + return false; + } + + display("'adb kill-server' succeeded."); //$NON-NLS-1$ + return true; + } + + /** + * Get the stderr/stdout outputs of a process and return when the process is done. + * Both must be read or the process will block on windows. + * @param process The process to get the ouput from + * @param errorOutput The array to store the stderr output. cannot be null. + * @param stdOutput The array to store the stdout output. cannot be null. + * @param waitforReaders if true, this will wait for the reader threads. + * @return the process return code. + * @throws InterruptedException + */ + private int grabProcessOutput(final Process process, final ArrayList errorOutput, + final ArrayList stdOutput, boolean waitforReaders) + throws InterruptedException { + assert errorOutput != null; + assert stdOutput != null; + // read the lines as they come. if null is returned, it's + // because the process finished + Thread t1 = new Thread("") { //$NON-NLS-1$ + @Override + public void run() { + // create a buffer to read the stderr output + InputStreamReader is = new InputStreamReader(process.getErrorStream()); + BufferedReader errReader = new BufferedReader(is); + + try { + while (true) { + String line = errReader.readLine(); + if (line != null) { + display("ADB Error: %1$s", line); + errorOutput.add(line); + } else { + break; + } + } + } catch (IOException e) { + // do nothing. + } + } + }; + + Thread t2 = new Thread("") { //$NON-NLS-1$ + @Override + public void run() { + InputStreamReader is = new InputStreamReader(process.getInputStream()); + BufferedReader outReader = new BufferedReader(is); + + try { + while (true) { + String line = outReader.readLine(); + if (line != null) { + display("ADB: %1$s", line); + stdOutput.add(line); + } else { + break; + } + } + } catch (IOException e) { + // do nothing. + } + } + }; + + t1.start(); + t2.start(); + + // it looks like on windows process#waitFor() can return + // before the thread have filled the arrays, so we wait for both threads and the + // process itself. + if (waitforReaders) { + try { + t1.join(); + } catch (InterruptedException e) { + } + try { + t2.join(); + } catch (InterruptedException e) { + } + } + + // get the return code from the process + return process.waitFor(); + } + +} diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java index 78b04f333..0d7179ce6 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java @@ -24,14 +24,34 @@ import java.util.Properties; */ public interface ISettingsPage { - /** Java system setting picked up by {@link URL} for http proxy port. Type: String. */ + /** + * Java system setting picked up by {@link URL} for http proxy port. + * Type: String. + */ public static final String KEY_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$ - /** Java system setting picked up by {@link URL} for http proxy host. Type: String. */ + /** + * Java system setting picked up by {@link URL} for http proxy host. + * Type: String. + */ public static final String KEY_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$ - /** Setting to force using http:// instead of https:// connections. Type: Boolean. */ + /** + * Setting to force using http:// instead of https:// connections. + * Type: Boolean. + * Default: False. + */ public static final String KEY_FORCE_HTTP = "sdkman.force.http"; //$NON-NLS-1$ - /** Setting to display only packages that are new or updates. Type: Boolean. */ + /** + * Setting to display only packages that are new or updates. + * Type: Boolean. + * Default: True. + */ public static final String KEY_SHOW_UPDATE_ONLY = "sdkman.show.update.only"; //$NON-NLS-1$ + /** + * Setting to ask for permission before restarting ADB. + * Type: Boolean. + * Default: True. + */ + public static final String KEY_ASK_ADB_RESTART = "sdkman.ask.adb.restart"; //$NON-NLS-1$ /** Loads settings from the given {@link Properties} container and update the page UI. */ public abstract void loadSettings(Properties in_settings); diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java index 8f5c4d2b5..1898f666c 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java @@ -38,13 +38,11 @@ import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Tree; diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java index b6363d304..a4b1a05de 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java @@ -26,7 +26,11 @@ import java.io.IOException; import java.util.Properties; /** - * + * Controller class to get settings values. Settings are kept in-memory. + * Users of this class must first load the settings before changing them and save + * them when modified. + *

+ * Settings are enumerated by constants in {@link ISettingsPage}. */ public class SettingsController { @@ -41,10 +45,30 @@ public class SettingsController { //--- Access to settings ------------ + /** + * Returns the value of the ISettingsPage#KEY_FORCE_HTTP setting. + * @see ISettingsPage#KEY_FORCE_HTTP + */ public boolean getForceHttp() { return Boolean.parseBoolean(mProperties.getProperty(ISettingsPage.KEY_FORCE_HTTP)); } + /** + * Returns the value of the ISettingsPage#KEY_ASK_ADB_RESTART setting. + * @see ISettingsPage#KEY_ASK_ADB_RESTART + */ + public boolean getAskBeforeAdbRestart() { + String value = mProperties.getProperty(ISettingsPage.KEY_ASK_ADB_RESTART); + if (value == null) { + return true; + } + return Boolean.parseBoolean(value); + } + + /** + * Returns the value of the ISettingsPage#KEY_SHOW_UPDATE_ONLY setting. + * @see ISettingsPage#KEY_SHOW_UPDATE_ONLY + */ public boolean getShowUpdateOnly() { String value = mProperties.getProperty(ISettingsPage.KEY_SHOW_UPDATE_ONLY); if (value == null) { @@ -53,8 +77,20 @@ public class SettingsController { return Boolean.parseBoolean(value); } + /** + * Sets the value of the ISettingsPage#KEY_SHOW_UPDATE_ONLY setting. + * @param enabled True if only compatible update items should be shown. + * @see ISettingsPage#KEY_SHOW_UPDATE_ONLY + */ public void setShowUpdateOnly(boolean enabled) { - mProperties.setProperty(ISettingsPage.KEY_SHOW_UPDATE_ONLY, Boolean.toString(enabled)); + setSetting(ISettingsPage.KEY_SHOW_UPDATE_ONLY, enabled); + } + + /** + * Internal helper to set a boolean setting. + */ + private void setSetting(String key, boolean value) { + mProperties.setProperty(key, Boolean.toString(value)); } //--- Controller methods ------------- @@ -89,6 +125,10 @@ public class SettingsController { fis = new FileInputStream(f); mProperties.load(fis); + + // Properly reformat some settings to enforce their default value when missing. + setShowUpdateOnly(getShowUpdateOnly()); + setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, getAskBeforeAdbRestart()); } } catch (AndroidLocationException e) { diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java index a605e3d73..bec00f8c4 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java @@ -34,6 +34,8 @@ import com.android.sdklib.internal.repository.Package.UpdateInfo; import com.android.sdkuilib.internal.repository.icons.ImageFactory; import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import java.io.ByteArrayOutputStream; @@ -294,14 +296,15 @@ class UpdaterData { break; } - if (archive.getParentPackage() instanceof AddonPackage) { - installedAddon = true; - } else if (archive.getParentPackage() instanceof ToolPackage) { - installedTools = true; - } - if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) { numInstalled++; + + // Check if we successfully installed a tool or add-on package. + if (archive.getParentPackage() instanceof AddonPackage) { + installedAddon = true; + } else if (archive.getParentPackage() instanceof ToolPackage) { + installedTools = true; + } } } catch (Throwable t) { @@ -334,8 +337,10 @@ class UpdaterData { // Update the USB vendor ids for adb try { mSdkManager.updateAdb(); + monitor.setResult("Updated ADB to support the USB devices declared in the SDK add-ons."); } catch (Exception e) { mSdkLog.error(e, "Update ADB failed"); + monitor.setResult("failed to update adb to support the USB devices declared in the SDK add-ons."); } } @@ -346,8 +351,11 @@ class UpdaterData { // before updating the tools folder, as adb.exe is (surprisingly) not // locked. - // TODO either bring in ddmlib and use its existing methods to stop adb - // or use a shell exec to tools/adb. + askForAdbRestart(monitor); + } + + if (installedTools) { + notifyToolsNeedsToBeRestarted(); } if (numInstalled == 0) { @@ -364,6 +372,54 @@ class UpdaterData { }); } + /** + * Attemps to restart ADB. + * + * If the "ask before restart" setting is set (the default), prompt the user whether + * now is a good time to restart ADB. + * @param monitor + */ + private void askForAdbRestart(ITaskMonitor monitor) { + final boolean[] canRestart = new boolean[] { true }; + + if (getSettingsController().getAskBeforeAdbRestart()) { + // need to ask for permission first + Display display = mWindowShell.getDisplay(); + + display.syncExec(new Runnable() { + public void run() { + canRestart[0] = MessageDialog.openQuestion(mWindowShell, + "ADB Restart", + "A package that depends on ADB has been updated. It is recommended " + + "to restart ADB. Is it OK to do it now? If not, you can restart it " + + "manually later."); + } + }); + } + + if (canRestart[0]) { + AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor); + adb.stopAdb(); + adb.startAdb(); + } + } + + private void notifyToolsNeedsToBeRestarted() { + Display display = mWindowShell.getDisplay(); + + display.syncExec(new Runnable() { + public void run() { + MessageDialog.openInformation(mWindowShell, + "Android Tools Updated", + "The Android SDK tool that you are currently using has been updated. " + + "It is recommended that you now close the Android SDK window and re-open it. " + + "If you started this window from Eclipse, please check if the Android " + + "plug-in needs to be updated."); + } + }); + } + + /** * Tries to update all the *existing* local packages. * This first refreshes all sources, then compares the available remote packages with