diff options
author | Pablo Leite <wpl020@motorola.com> | 2011-05-30 15:14:14 -0300 |
---|---|---|
committer | Pablo Leite <wpl020@motorola.com> | 2011-08-11 17:06:28 -0300 |
commit | 924958e21bbe1d3c918235d9ca78a6dc64c7879b (patch) | |
tree | 7056015ca3fc9ae9ac622467da481f52197ea8df | |
parent | 8b7e1b8e01a90c08b94880aa0ddc68135593a309 (diff) | |
download | sdk-924958e21bbe1d3c918235d9ca78a6dc64c7879b.zip sdk-924958e21bbe1d3c918235d9ca78a6dc64c7879b.tar.gz sdk-924958e21bbe1d3c918235d9ca78a6dc64c7879b.tar.bz2 |
Add support for authenticated add-on servers.
Displays a login prompt if necessary.
Note: This code depends on Apache http-client libraries.
Build files had been modified in order to add this dependency.
http-client libraries had also be added on prebuilt project.
prebuilt changeID=I084d78dd09a431bc3a2d77e77810b84c693bdcb7
GerritLink=https://review.source.android.com/#change,23387
Change-Id: Icada9b41a21fe3aacef9a1eff209a3fe5591a4e0
20 files changed, 620 insertions, 55 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath index a77b6b6..72a5c8b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath +++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath @@ -1,6 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry excluding="Makefile|resources/" kind="src" path="src"/> + <classpathentry kind="lib" path="libs/commons-codec-1.4.jar"/> + <classpathentry kind="lib" path="libs/commons-logging-1.1.1.jar"/> + <classpathentry kind="lib" path="libs/httpclient-4.1.1.jar"/> + <classpathentry kind="lib" path="libs/httpcore-4.1.jar"/> + <classpathentry kind="lib" path="libs/httpmime-4.1.1.jar"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> <classpathentry kind="lib" path="libs/androidprefs.jar" sourcepath="/AndroidPrefs"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF index 49f73e6..ebcf44f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF @@ -14,7 +14,13 @@ Bundle-ClassPath: ., libs/layoutlib_api.jar, libs/ide_common.jar, libs/common.jar, - libs/assetstudio.jar + libs/assetstudio.jar, + libs/httpclient-4.1.1.jar, + libs/httpcore-4.1.jar, + libs/httpmime-4.1.1.jar, + libs/httpclient-4.1.1.jar, + libs/commons-logging-1.1.1.jar, + libs/commons-codec-1.4.jar Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin Bundle-Vendor: The Android Open Source Project Require-Bundle: com.android.ide.eclipse.ddms, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/build.properties b/eclipse/plugins/com.android.ide.eclipse.adt/build.properties index 96d191e..a82e779 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/build.properties +++ b/eclipse/plugins/com.android.ide.eclipse.adt/build.properties @@ -8,7 +8,12 @@ bin.includes = plugin.xml,\ about.properties,\ NOTICE,\ about.html,\ - libs/common.jar + libs/common.jar,\ + libs/httpclient-4.1.1.jar,\ + libs/httpcore-4.1.jar,\ + libs/httpmime-4.1.1.jar,\ + libs/commons-logging-1.1.1.jar,\ + libs/commons-codec-1.4.jar source.. = src/ output.. = bin/ bin.excludes = libs/.gitignore diff --git a/sdkmanager/app/etc/android.bat b/sdkmanager/app/etc/android.bat index f616dd7..3233ebd 100755 --- a/sdkmanager/app/etc/android.bat +++ b/sdkmanager/app/etc/android.bat @@ -61,6 +61,11 @@ if not "%1"=="" goto EndTempCopy copy /B /D /Y lib\common.jar %tmp_dir%\lib\ > nul
copy /B /D /Y lib\commons-compress* %tmp_dir%\lib\ > nul
copy /B /D /Y lib\swtmenubar.jar %tmp_dir%\lib\ > nul
+ copy /B /D /Y lib\commons-logging* %tmp_dir%\lib\ > nul
+ copy /B /D /Y lib\commons-codec* %tmp_dir%\lib\ > nul
+ copy /B /D /Y lib\httpclient* %tmp_dir%\lib\ > nul
+ copy /B /D /Y lib\httpcode* %tmp_dir%\lib\ > nul
+ copy /B /D /Y lib\httpmime* %tmp_dir%\lib\ > nul rem jar_path and swt_path are relative to PWD so we don't need to adjust them, just change dirs.
set tools_dir=%cd%
diff --git a/sdkmanager/libs/sdklib/.classpath b/sdkmanager/libs/sdklib/.classpath index 7cabaa0..c48e0bf 100644 --- a/sdkmanager/libs/sdklib/.classpath +++ b/sdkmanager/libs/sdklib/.classpath @@ -7,5 +7,10 @@ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/commons-compress/commons-compress-1.0.jar"/> <classpathentry combineaccessrules="false" kind="src" path="/common"/> + <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/http-client/commons-codec-1.4.jar"/> + <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/http-client/commons-logging-1.1.1.jar"/> + <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/http-client/httpclient-4.1.1.jar"/> + <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/http-client/httpcore-4.1.jar"/> + <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/http-client/httpmime-4.1.1.jar"/> <classpathentry kind="output" path="bin"/> </classpath>
\ No newline at end of file diff --git a/sdkmanager/libs/sdklib/Android.mk b/sdkmanager/libs/sdklib/Android.mk index 7ed009c..ab800da 100644 --- a/sdkmanager/libs/sdklib/Android.mk +++ b/sdkmanager/libs/sdklib/Android.mk @@ -28,7 +28,12 @@ LOCAL_JAR_MANIFEST := manifest.txt LOCAL_JAVA_LIBRARIES := \ androidprefs \ common \ - commons-compress-1.0 + commons-compress-1.0 \ + httpclient-4.1.1 \ + httpcore-4.1 \ + httpmime-4.1.1 \ + commons-logging-1.1.1 \ + commons-codec-1.4 LOCAL_MODULE := sdklib diff --git a/sdkmanager/libs/sdklib/manifest.txt b/sdkmanager/libs/sdklib/manifest.txt index 0ead4fc..0efb701 100644 --- a/sdkmanager/libs/sdklib/manifest.txt +++ b/sdkmanager/libs/sdklib/manifest.txt @@ -1 +1 @@ -Class-Path: androidprefs.jar common.jar commons-compress-1.0.jar +Class-Path: androidprefs.jar common.jar commons-compress-1.0.jar httpclient-4.1.1.jar httpcore-4.1.jar httpmime-4.1.1.jar commons-logging-1.1.1.jar commons-codec-1.4.jar diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java index 11dad83..93a8a98 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java @@ -31,7 +31,6 @@ import java.io.ByteArrayInputStream; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.regex.Matcher;
@@ -91,7 +90,7 @@ public class AddonsListFetcher { url = url == null ? "" : url.trim();
- monitor.setProgressMax(4);
+ monitor.setProgressMax(5);
monitor.setDescription("Fetching %1$s", url);
monitor.incProgress(1);
@@ -101,7 +100,7 @@ public class AddonsListFetcher { Document validatedDoc = null;
String validatedUri = null;
- ByteArrayInputStream xml = fetchUrl(url, exception);
+ ByteArrayInputStream xml = fetchUrl(url, monitor.createSubMonitor(1), exception);
if (xml != null) {
monitor.setDescription("Validate XML");
@@ -176,21 +175,19 @@ public class AddonsListFetcher { }
/**
- * Fetches the document at the given URL and returns it as a stream.
- * Returns null if anything wrong happens.
- *
- * References: <br/>
- * Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html <br/>
- * Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html <br/>
- * Java set Proxy: http://java.sun.com/docs/books/tutorial/networking/urls/_setProxy.html <br/>
+ * Fetches the document at the given URL and returns it as a stream. Returns
+ * null if anything wrong happens. References: <br/>
+ * URL Connection:
*
+ * @see {@link UrlOpener} which handles all URL logic.
* @param urlString The URL to load, as a string.
- * @param outException If non null, where to store any exception that happens during the fetch.
+ * @param monitor {@link ITaskMonitor} related to this URL.
+ * @param outException If non null, where to store any exception that
+ * happens during the fetch.
*/
- private ByteArrayInputStream fetchUrl(String urlString, Exception[] outException) {
- URL url;
+ private ByteArrayInputStream fetchUrl(String urlString, ITaskMonitor monitor,
+ Exception[] outException) {
try {
- url = new URL(urlString);
InputStream is = null;
@@ -199,7 +196,7 @@ public class AddonsListFetcher { byte[] result = new byte[inc];
try {
- is = url.openStream();
+ is = UrlOpener.openURL(urlString, monitor);
int n;
while ((n = is.read(result, curr, result.length - curr)) != -1) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java index 552c7bb..06e64df 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java @@ -30,7 +30,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
@@ -38,7 +37,6 @@ import java.util.HashSet; import java.util.Properties;
import java.util.Set;
-
/**
* Performs the work of installing a given {@link Archive}.
*/
@@ -267,15 +265,13 @@ public class ArchiveInstaller { String urlString,
String description,
ITaskMonitor monitor) {
- URL url;
description += " (%1$d%%, %2$.0f KiB/s, %3$d %4$s left)";
FileOutputStream os = null;
InputStream is = null;
try {
- url = new URL(urlString);
- is = url.openStream();
+ is = UrlOpener.openURL(urlString, monitor);
os = new FileOutputStream(tmpFile);
MessageDigest digester = archive.getChecksumType().getMessageDigest();
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java index 313f36e..4a1a1e0 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java @@ -17,6 +17,7 @@ package com.android.sdklib.internal.repository;
import com.android.sdklib.ISdkLog;
+import com.android.util.Pair;
/**
@@ -131,4 +132,20 @@ public interface ITaskMonitor extends ISdkLog { * @return true if YES was clicked.
*/
public boolean displayPrompt(final String title, final String message);
+
+ /**
+ * Launch an interface which asks for login and password. Implementations
+ * MUST allow this to be called from any thread, e.g. by making sure the
+ * dialog is opened synchronously in the UI thread.
+ *
+ * @param title The title of the dialog box.
+ * @param message The message to be displayed as an instruction.
+ * @return Returns a {@link Pair} holding the entered login and password.
+ * The information must always be in the following order:
+ * Login,Password. So in order to retrieve the <b>login</b> callers
+ * should retrieve the first element, and the second value for the
+ * <b>password</b>.
+ If operation is <b>canceled</b> by user the return value must be <b>null</b>.
+ */
+ public Pair<String, String> displayLoginPasswordPrompt(String title, String message); }
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java index afe291f..8cd52d6 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java @@ -18,6 +18,7 @@ package com.android.sdklib.internal.repository; import com.android.sdklib.ISdkLog;
import com.android.sdklib.NullSdkLog;
+import com.android.util.Pair;
/**
@@ -92,6 +93,11 @@ public class NullTaskMonitor implements ITaskMonitor { return false;
}
+ /** Always return null. */
+ public Pair<String, String> displayLoginPasswordPrompt(String title, String message) {
+ return null;
+ }
+
// --- ISdkLog ---
public void error(Throwable t, String errorFormat, Object... args) {
@@ -105,4 +111,5 @@ public class NullTaskMonitor implements ITaskMonitor { public void printf(String msgFormat, Object... args) {
mLog.printf(msgFormat, args);
}
+
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java index 22b9c42..a5b16c3 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java @@ -249,7 +249,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> { */
public void load(ITaskMonitor monitor, boolean forceHttp) {
- monitor.setProgressMax(4);
+ monitor.setProgressMax(6);
setDefaultDescription();
@@ -265,7 +265,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> { Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
String[] validationError = new String[] { null };
Exception[] exception = new Exception[] { null };
- InputStream xml = fetchUrl(url, exception);
+ InputStream xml = fetchUrl(url, monitor.createSubMonitor(1), exception);
Document validatedDoc = null;
boolean usingAlternateXml = false;
boolean usingAlternateUrl = false;
@@ -279,13 +279,15 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> { }
url += getUrlDefaultXmlFile();
- xml = fetchUrl(url, exception);
+ xml = fetchUrl(url, monitor.createSubMonitor(1), exception);
usingAlternateUrl = true;
}
if (xml != null) {
monitor.setDescription(String.format("Validate XML: %1$s", url));
+ ITaskMonitor subMonitor = monitor.createSubMonitor(2);
+ subMonitor.setProgressMax(2);
for (int tryOtherUrl = 0; tryOtherUrl < 2; tryOtherUrl++) {
// Explore the XML to find the potential XML schema version
int version = getXmlSchemaVersion(xml);
@@ -347,8 +349,8 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> { }
url += getUrlDefaultXmlFile();
- xml = fetchUrl(url, null /*outException*/);
-
+ xml = fetchUrl(url, subMonitor.createSubMonitor(1), null /* outException */);
+ subMonitor.incProgress(1);
// Loop to try the alternative document
if (xml != null) {
usingAlternateUrl = true;
@@ -465,21 +467,19 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> { }
/**
- * Fetches the document at the given URL and returns it as a string.
- * Returns null if anything wrong happens and write errors to the monitor.
- *
+ * Fetches the document at the given URL and returns it as a string. Returns
+ * null if anything wrong happens and write errors to the monitor.
* References: <br/>
- * Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html <br/>
- * Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html <br/>
- * Java set Proxy: http://java.sun.com/docs/books/tutorial/networking/urls/_setProxy.html <br/>
+ * URL Connection:
*
+ * @see {@link UrlOpener} which handles all URL logic.
* @param urlString The URL to load, as a string.
- * @param outException If non null, where to store any exception that happens during the fetch.
+ * @param monitor {@link ITaskMonitor} related to this URL.
+ * @param outException If non null, where to store any exception that
+ * happens during the fetch.
*/
- private InputStream fetchUrl(String urlString, Exception[] outException) {
- URL url;
+ private InputStream fetchUrl(String urlString, ITaskMonitor monitor, Exception[] outException) {
try {
- url = new URL(urlString);
InputStream is = null;
@@ -488,7 +488,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> { byte[] result = new byte[inc];
try {
- is = url.openStream();
+ is = UrlOpener.openURL(urlString, monitor);
int n;
while ((n = is.read(result, curr, result.length - curr)) != -1) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java new file mode 100644 index 0000000..a99baf6 --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2011 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.sdklib.internal.repository; + +import com.android.util.Pair; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.ProxySelectorRoutePlanner; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; + +import java.io.IOException; +import java.io.InputStream; +import java.net.ProxySelector; +import java.util.HashMap; +import java.util.Map; + +/** + * This class holds methods for adding URLs management. + */ +public class UrlOpener { + + private static Map<String, Pair<String, String>> sRealmCache = + new HashMap<String, Pair<String, String>>(); + + /** + * Open a URL. It can be a simple URL or one which requires basic + * authentication. </br> <i>Description</i> + * <p> + * Tries to access the given URL. If http response is either + * {@link HttpStatus.SC_UNAUTHORIZED} or + * {@link HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED} asks for + * login/password and tries to authenticate into proxy server and/or URL. + * </p> + * <p> + * This implementation relies on the Apache Http Client due to its + * capabilities of proxy/http authentication. </br> Proxy configuration is + * determined by {@link ProxySelectorRoutePlanner} using the JVM proxy + * settings by default. For more information see: + * <a>http://hc.apache.org/httpcomponents-client-ga/</a> + * <a>http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/conn/ProxySelectorRoutePlanner.html</a> + * </p> + * <p> + * There's a very simple<b>cache</b> implementation. Login/Password for each + * realm are stored on a static {@link Map}. Before asking the user the + * method verifies if the information is already available in cache + * </p> + * + * @param url the URL string to be opened. + * @param monitor {@link ITaskMonitor} which is related to this URL + * fetching. + * @return Returns an {@link InputStream} holding the URL content. + * @throws IOException Exception thrown when there are problems retrieving + * the URL or its content. + */ + static InputStream openURL(String url, ITaskMonitor monitor) throws IOException { + + InputStream stream = null; + HttpEntity entity = null; + Pair<String, String> result = null; + String realm = null; + + // use the simple one + DefaultHttpClient httpclient = new DefaultHttpClient(); + + // create local execution context + HttpContext localContext = new BasicHttpContext(); + HttpGet httpget = new HttpGet(url); + + // retrieve local java configured network in case there is the need to + // authenticate a proxy + ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(httpclient + .getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault()); + httpclient.setRoutePlanner(routePlanner); + + boolean trying = true; + // loop while the response is being fetched + while (trying) { + // connect and get status code + HttpResponse response = httpclient.execute(httpget, localContext); + int statusCode = response.getStatusLine().getStatusCode(); + + // check whether any authentication is required + AuthState authenticationState = null; + if (statusCode == HttpStatus.SC_UNAUTHORIZED) { + // Target host authentication required + authenticationState = (AuthState) localContext + .getAttribute(ClientContext.TARGET_AUTH_STATE); + } + if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) { + // Proxy authentication required + authenticationState = (AuthState) localContext + .getAttribute(ClientContext.PROXY_AUTH_STATE); + } + if (statusCode == HttpStatus.SC_OK) { + // in case the status is OK and there is a realm and result, + // cache it + if (realm != null && result != null) { + sRealmCache.put(realm, result); + } + } + + // there is the need for authentication + if (authenticationState != null) { + + // get scope and realm + AuthScope authScope = authenticationState.getAuthScope(); + + // If the current realm is different from the last one it means + // a pass was performed successfully to the last URL, therefore + // cache the last realm + if (realm != null && !realm.equals(authScope.getRealm())) { + sRealmCache.put(realm, result); + } + + realm = authScope.getRealm(); + + // in case there is cache for this Realm, use it to authenticate + if (sRealmCache.containsKey(realm)) { + result = sRealmCache.get(realm); + } else { + // since there is no cache, request for login and password + result = monitor.displayLoginPasswordPrompt("Site Authentication", + "Please login to the following domain: " + realm + + "\n\nServer requiring authentication:\n" + authScope.getHost()); + if (result == null) { + throw new IOException("User canceled login dialog."); + } + } + + // retrieve authentication data + String user = result.getFirst(); + String password = result.getSecond(); + + // proceed in case there is indeed a user + if (user != null && user.length() > 0) { + Credentials credentials = new UsernamePasswordCredentials(user, password); + httpclient.getCredentialsProvider().setCredentials(authScope, credentials); + trying = true; + } else { + trying = false; + } + } else { + trying = false; + } + entity = response.getEntity(); + if (entity != null) { + // in case another pass to the Http Client will be performed, + // consume the entity + if (trying) { + entity.consumeContent(); + } + // since no pass to the Http Client is needed, retrieve the + // entity's content + else { + // Use a buffered entity because the stream in which it will + // be transfered, will not be closed later, unexpectedly + BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity); + stream = bufferedEntity.getContent(); + } + } + } + + // since Http Client is no longer needed, close it + httpclient.getConnectionManager().shutdown(); + + return stream; + } +} diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockMonitor.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockMonitor.java index c006707..e61184d 100755 --- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockMonitor.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockMonitor.java @@ -16,6 +16,7 @@ package com.android.sdklib.internal.repository;
+import com.android.util.Pair;
/**
* Mock implementation of {@link ITaskMonitor} that simply captures
@@ -95,4 +96,8 @@ public class MockMonitor implements ITaskMonitor { public void warning(String warningFormat, Object... args) {
}
+ + public Pair<String, String> displayLoginPasswordPrompt(String title, String message) {
+ return null;
+ }
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterNoWindow.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterNoWindow.java index ef4864b..10f4074 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterNoWindow.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterNoWindow.java @@ -23,7 +23,9 @@ import com.android.sdklib.internal.repository.ITaskFactory; import com.android.sdklib.internal.repository.ITaskMonitor; import com.android.sdklib.internal.repository.NullTaskMonitor; import com.android.sdklib.repository.SdkRepoConstants; +import com.android.util.Pair; +import java.io.IOException; import java.util.ArrayList; import java.util.Properties; @@ -336,6 +338,74 @@ public class SdkUpdaterNoWindow { } /** + * Displays a prompt message to the user and read two values, + * login/password. + * <p> + * <i>Asks user for login/password information.</i> + * <p> + * This method shows a question in the standard output, asking for login + * and password.</br> + * <b>Method Output:</b></br> + * Title</br> + * Message</br> + * Login: (Wait for user input)</br> + * Password: (Wait for user input)</br> + * <p> + * + * @param title The title of the iteration. + * @param message The message to be displayed. + * @return A {@link Pair} holding the entered login and password. The + * <b>first element</b> is always the <b>Login</b>, and the + * <b>second element</b> is always the <b>Password</b>. This + * method will never return null, in case of error the pair will + * be filled with empty strings. + * @see ITaskMonitor#displayLoginPasswordPrompt(String, String) + */ + public Pair<String, String> displayLoginPasswordPrompt(String title, String message) { + String login = ""; //$NON-NLS-1$ + String password = ""; //$NON-NLS-1$ + mSdkLog.printf("\n%1$s\n%2$s", title, message); + byte[] readBuffer = new byte[2048]; + try { + mSdkLog.printf("\nLogin: "); + login = readLine(readBuffer); + mSdkLog.printf("\nPassword: "); + password = readLine(readBuffer); + /* + * TODO: Implement a way to don't echo the typed password On + * Java 5 there's no simple way to do this. There's just a + * workaround which is output backspaces on each keystroke. + * A good alternative is to use Java 6 java.io.Console + */ + } catch (IOException e) { + // Reset login/pass to empty Strings. + login = ""; //$NON-NLS-1$ + password = ""; //$NON-NLS-1$ + //Just print the error to console. + mSdkLog.printf("\nError occurred during login/pass query: %s\n", e.getMessage()); + } + + return Pair.of(login, password); + } + + private String readLine(byte[] buffer) throws IOException { + int count = System.in.read(buffer); + + // is the input longer than the buffer? + if (count == buffer.length && buffer[count-1] != 10) { + throw new IOException(String.format( + "Input is longer than the buffer size, (%1$s) bytes", buffer.length)); + } + + // ignore end whitespace + while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) { + count--; + } + + return new String(buffer, 0, count); + } + + /** * Creates a sub-monitor that will use up to tickCount on the progress bar. * tickCount must be 1 or more. */ @@ -433,6 +503,10 @@ public class SdkUpdaterNoWindow { return mRoot.displayPrompt(title, message); } + public Pair<String, String> displayLoginPasswordPrompt(String title, String message) { + return mRoot.displayLoginPasswordPrompt(title, message); + } + public ITaskMonitor createSubMonitor(int tickCount) { assert mSubCoef > 0; assert tickCount > 0; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/IProgressUiProvider.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/IProgressUiProvider.java index 4a7922d..f6e13c6 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/IProgressUiProvider.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/IProgressUiProvider.java @@ -17,6 +17,7 @@ package com.android.sdkuilib.internal.tasks; import com.android.sdklib.internal.repository.ITaskMonitor; +import com.android.util.Pair; import org.eclipse.swt.widgets.ProgressBar; @@ -90,4 +91,20 @@ interface IProgressUiProvider { */ public abstract boolean displayPrompt(String title, String message); + /** + * Launch an interface which asks for login and password. Implementations + * MUST allow this to be called from any thread, e.g. by making sure the + * dialog is opened synchronously in the UI thread. + * + * @param title The title of the dialog box. + * @param message The message to be displayed as an instruction. + * @return Returns a {@link Pair} holding the entered login and password. + * The information must always be in the following order: + * Login,Password. So in order to retrieve the <b>login</b> callers + * should retrieve the first element, and the second value for the + * <b>password</b>. This method should never return a null pair. + * It's elements however can be empty Strings + */ + public abstract Pair<String, String> displayLoginPasswordPrompt(String title, String message); + } diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskDialog.java index a4236fa..dbc871f 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskDialog.java @@ -18,6 +18,9 @@ package com.android.sdkuilib.internal.tasks; import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdkuilib.ui.AuthenticationDialog;
+import com.android.sdkuilib.ui.GridDialog;
+import com.android.util.Pair;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
@@ -154,7 +157,8 @@ final class ProgressTaskDialog extends Dialog implements IProgressUiProvider { });
mResultText = new Text(mRootComposite,
- SWT.BORDER | SWT.READ_ONLY | SWT.WRAP | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI);
+ SWT.BORDER | SWT.READ_ONLY | SWT.WRAP |
+ SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI);
mResultText.setEditable(true);
mResultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
}
@@ -393,6 +397,45 @@ final class ProgressTaskDialog extends Dialog implements IProgressUiProvider { }
/**
+ * This method opens a pop-up window which requests for User Login and
+ * password.
+ *
+ * @param title The title of the window.
+ * @param message The message to displayed in the login/password window.
+ * @return Returns a {@link Pair} holding the entered login and password.
+ * The information must always be in the following order:
+ * Login,Password. So in order to retrieve the <b>login</b> callers
+ * should retrieve the first element, and the second value for the
+ * <b>password</b>.
+ * If operation is <b>canceled</b> by user the return value must be <b>null</b>.
+ * @see ITaskMonitor#displayLoginPasswordPrompt(String, String)
+ */
+ public Pair<String, String> displayLoginPasswordPrompt(
+ final String title, final String message) {
+ final String[] resultArray = new String[2];
+ Display display = mDialogShell.getDisplay();
+
+ // open dialog and request login and password
+ display.syncExec(new Runnable() {
+ public void run() {
+ AuthenticationDialog authenticationDialog = new AuthenticationDialog(mDialogShell,
+ title,
+ message);
+ int dlgResult= authenticationDialog.open();
+ if(dlgResult == GridDialog.OK) {
+ resultArray[0] = authenticationDialog.getLogin();
+ resultArray[1] = authenticationDialog.getPassword();
+ } else {
+ resultArray[0] = null;
+ resultArray[1] = null;
+ }
+ }
+ });
+
+ return resultArray[0] == null ? null : Pair.of(resultArray[0], resultArray[1]);
+ }
+
+ /**
* Starts the thread that runs the task.
* This is deferred till the UI is created.
*/
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java index d90eaed..9485885 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java @@ -19,11 +19,13 @@ package com.android.sdkuilib.internal.tasks; import com.android.sdklib.ISdkLog;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdkuilib.ui.AuthenticationDialog;
+import com.android.sdkuilib.ui.GridDialog;
+import com.android.util.Pair;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
@@ -325,20 +327,53 @@ public final class ProgressView implements IProgressUiProvider { public boolean displayPrompt(final String title, final String message) {
final boolean[] result = new boolean[] { false };
- if (!mProgressBar.isDisposed()) {
- final Shell shell = mProgressBar.getShell();
- Display display = shell.getDisplay();
-
- display.syncExec(new Runnable() {
- public void run() {
- result[0] = MessageDialog.openQuestion(shell, title, message);
- }
- });
- }
+ syncExec(mProgressBar, new Runnable() {
+ public void run() {
+ Shell shell = mProgressBar.getShell();
+ result[0] = MessageDialog.openQuestion(shell, title, message);
+ }
+ });
return result[0];
}
+ /**
+ * This method opens a pop-up window which requests for User Login and
+ * password.
+ *
+ * @param title The title of the window.
+ * @param message The message to displayed in the login/password window.
+ * @return Returns a {@link Pair} holding the entered login and password.
+ * The information must always be in the following order:
+ * Login,Password. So in order to retrieve the <b>login</b> callers
+ * should retrieve the first element, and the second value for the
+ * <b>password</b>.
+ * If operation is <b>canceled</b> by user the return value must be <b>null</b>.
+ * @see ITaskMonitor#displayLoginPasswordPrompt(String, String)
+ */
+ public Pair<String, String>
+ displayLoginPasswordPrompt(final String title, final String message) {
+ final String[] resultArray = new String[] {"", ""};
+ // open dialog and request login and password
+ syncExec(mProgressBar, new Runnable() {
+ public void run() {
+ Shell shell = mProgressBar.getShell();
+ AuthenticationDialog authenticationDialog = new AuthenticationDialog(shell,
+ title,
+ message);
+ int dlgResult = authenticationDialog.open();
+ if (dlgResult == GridDialog.OK) {
+ resultArray[0] = authenticationDialog.getLogin();
+ resultArray[1] = authenticationDialog.getPassword();
+ } else {
+ resultArray[0] = null;
+ resultArray[1] = null;
+ }
+ }
+ });
+ return resultArray[0] == null ? null : Pair.of(resultArray[0], resultArray[1]);
+ }
+
// ----
/**
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/TaskMonitorImpl.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/TaskMonitorImpl.java index 5286df5..f3dff37 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/TaskMonitorImpl.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/TaskMonitorImpl.java @@ -17,8 +17,7 @@ package com.android.sdkuilib.internal.tasks;
import com.android.sdklib.internal.repository.ITaskMonitor;
-
-import org.eclipse.swt.widgets.ProgressBar;
+import com.android.util.Pair;
/**
* Internal class that implements the logic of an {@link ITaskMonitor}.
@@ -156,7 +155,7 @@ class TaskMonitorImpl implements ITaskMonitor { }
/**
- * Display a yes/no question dialog box.
+ * Displays a yes/no question dialog box.
*
* This implementation allow this to be called from any thread, it
* makes sure the dialog is opened synchronously in the ui thread.
@@ -170,6 +169,21 @@ class TaskMonitorImpl implements ITaskMonitor { }
/**
+ * Displays a Login/Password dialog. This implementation allows this method to be
+ * called from any thread, it makes sure the dialog is opened synchronously
+ * in the ui thread.
+ *
+ * @param title The title of the dialog box
+ * @param message Message to be displayed
+ * @return Pair with entered login/password. Login is always the first
+ * element and Password is always the second. If any error occurs a
+ * pair with empty strings is returned.
+ */
+ public Pair<String, String> displayLoginPasswordPrompt(String title, String message) {
+ return mUi.displayLoginPasswordPrompt(title, message);
+ }
+
+ /**
* Creates a sub-monitor that will use up to tickCount on the progress bar.
* tickCount must be 1 or more.
*/
@@ -284,6 +298,10 @@ class TaskMonitorImpl implements ITaskMonitor { return mRoot.displayPrompt(title, message);
}
+ public Pair<String, String> displayLoginPasswordPrompt(String title, String message) {
+ return mRoot.displayLoginPasswordPrompt(title, message);
+ }
+
public ITaskMonitor createSubMonitor(int tickCount) {
assert mSubCoef > 0;
assert tickCount > 0;
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/AuthenticationDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/AuthenticationDialog.java new file mode 100644 index 0000000..497c752 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/AuthenticationDialog.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 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.ui; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Dialog which collects from the user his/her login and password. + */ +public class AuthenticationDialog extends GridDialog { + + private Text mTxtLogin; + + private Text mTxtPassword; + + private String mTitle; + + private String mMessage; + + protected String mLogin; + + protected String mPassword; + + /** + * Constructor which retrieves the parent {@link Shell} and the message to + * be displayed in this dialog. + * + * @param parentShell Parent Shell + * @param title Title of the window. + * @param message Message the be displayed in this dialog. + */ + public AuthenticationDialog(Shell parentShell, String title, String message) { + super(parentShell, 1, false); + // assign fields + mTitle = title; + mMessage = message; + } + + @Override + public void createDialogContent(Composite parent) { + // Configure Dialog + getShell().setText(mTitle); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + parent.setLayoutData(data); + + // Upper Composite + Composite upperComposite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(2, false); + layout.verticalSpacing = 10; + upperComposite.setLayout(layout); + data = new GridData(SWT.FILL, SWT.CENTER, true, true); + upperComposite.setLayoutData(data); + + // add message label + Label lblMessage = new Label(upperComposite, SWT.WRAP); + lblMessage.setText(mMessage); + data = new GridData(SWT.FILL, SWT.CENTER, true, true, 2, 1); + data.widthHint = 500; + lblMessage.setLayoutData(data); + + // add user name label and text field + Label lblUserName = new Label(upperComposite, SWT.NONE); + lblUserName.setText("Login:"); + data = new GridData(SWT.LEFT, SWT.CENTER, false, false); + lblUserName.setLayoutData(data); + + mTxtLogin = new Text(upperComposite, SWT.SINGLE | SWT.BORDER); + data = new GridData(SWT.FILL, SWT.CENTER, true, false); + mTxtLogin.setLayoutData(data); + mTxtLogin.setFocus(); + mTxtLogin.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent arg0) { + mLogin = mTxtLogin.getText(); + } + }); + + // add password label and text field + Label lblPassword = new Label(upperComposite, SWT.NONE); + lblPassword.setText("Password:"); + data = new GridData(SWT.LEFT, SWT.CENTER, false, false); + lblPassword.setLayoutData(data); + + mTxtPassword = new Text(upperComposite, SWT.SINGLE | SWT.PASSWORD | SWT.BORDER); + data = new GridData(SWT.FILL, SWT.CENTER, true, false); + mTxtPassword.setLayoutData(data); + mTxtPassword.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent arg0) { + mPassword = mTxtPassword.getText(); + } + }); + } + + /** + * Retrieves the Login field information + * + * @return Login field value or empty String. Return value is never null + */ + public String getLogin() { + return mLogin != null ? mLogin : ""; //$NON-NLS-1$ + } + + /** + * Retrieves the Password field information + * + * @return Password field value or empty String. Return value is never null + */ + public String getPassword() { + return mPassword != null ? mPassword : ""; //$NON-NLS-1$ + } +} |