From 7762d93621f508f4410fd37b5bbdfec0071b0420 Mon Sep 17 00:00:00 2001 From: Paul Westbrook Date: Fri, 11 Dec 2009 14:13:48 -0800 Subject: Unbundling work Moved AndroidHttpClient, Rfc822InputFilter, Rfc822Validator NumberPicker, NumberPickerButton to android-common --- --- .../java/com/android/common/AndroidHttpClient.java | 497 ++++++++++++++++++++ .../java/com/android/common/Rfc822InputFilter.java | 74 +++ .../java/com/android/common/Rfc822Validator.java | 134 ++++++ .../com/android/common/widget/NumberPicker.java | 412 +++++++++++++++++ .../android/common/widget/NumberPickerButton.java | 86 ++++ core/java/android/app/ActivityThread.java | 3 - core/java/android/net/http/AndroidHttpClient.java | 508 --------------------- core/java/android/text/util/Rfc822InputFilter.java | 58 --- core/java/android/text/util/Rfc822Validator.java | 132 ------ core/java/android/widget/DatePicker.java | 4 +- core/java/android/widget/TimePicker.java | 3 +- .../com/android/internal/widget/NumberPicker.java | 411 ----------------- .../internal/widget/NumberPickerButton.java | 86 ---- .../com/google/android/net/GoogleHttpClient.java | 2 +- .../internal/location/GpsXtraDownloader.java | 5 +- preloaded-classes | 9 - .../android/unit_tests/GoogleHttpClientTest.java | 11 +- .../src/com/android/unit_tests/TextUtilsTest.java | 8 +- tests/SslLoad/src/com/android/sslload/SslLoad.java | 2 +- 19 files changed, 1223 insertions(+), 1222 deletions(-) create mode 100644 common/java/com/android/common/AndroidHttpClient.java create mode 100644 common/java/com/android/common/Rfc822InputFilter.java create mode 100644 common/java/com/android/common/Rfc822Validator.java create mode 100644 common/java/com/android/common/widget/NumberPicker.java create mode 100644 common/java/com/android/common/widget/NumberPickerButton.java delete mode 100644 core/java/android/net/http/AndroidHttpClient.java delete mode 100644 core/java/android/text/util/Rfc822InputFilter.java delete mode 100644 core/java/android/text/util/Rfc822Validator.java delete mode 100644 core/java/com/android/internal/widget/NumberPicker.java delete mode 100644 core/java/com/android/internal/widget/NumberPickerButton.java diff --git a/common/java/com/android/common/AndroidHttpClient.java b/common/java/com/android/common/AndroidHttpClient.java new file mode 100644 index 0000000..6fa6da1 --- /dev/null +++ b/common/java/com/android/common/AndroidHttpClient.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2007 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.common; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.entity.AbstractHttpEntity; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.params.HttpClientParams; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.RequestWrapper; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; +import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import java.net.URI; +import java.security.KeyManagementException; + +import android.content.ContentResolver; +import android.os.Looper; +import android.os.SystemProperties; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +/** + * Subclass of the Apache {@link DefaultHttpClient} that is configured with + * reasonable default settings and registered schemes for Android, and + * also lets the user add {@link HttpRequestInterceptor} classes. + * Don't create this directly, use the {@link #newInstance} factory method. + * + *

This client processes cookies but does not retain them by default. + * To retain cookies, simply add a cookie store to the HttpContext:

+ * + *
context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
+ * + * {@hide} + */ +public final class AndroidHttpClient implements HttpClient { + + // Gzip of data shorter than this probably won't be worthwhile + public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256; + + private static final String TAG = "AndroidHttpClient"; + + + /** Interceptor throws an exception if the executing thread is blocked */ + private static final HttpRequestInterceptor sThreadCheckInterceptor = + new HttpRequestInterceptor() { + public void process(HttpRequest request, HttpContext context) { + // Prevent the HttpRequest from being sent on the main thread + if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) { + throw new RuntimeException("This thread forbids HTTP requests"); + } + } + }; + + /** + * Create a new HttpClient with reasonable defaults (which you can update). + * + * @param userAgent to report in your HTTP requests. + * @param sessionCache persistent session cache + * @return AndroidHttpClient for you to use for all your requests. + */ + public static AndroidHttpClient newInstance(String userAgent, + SSLClientSessionCache sessionCache) { + HttpParams params = new BasicHttpParams(); + + // Turn off stale checking. Our connections break all the time anyway, + // and it's not worth it to pay the penalty of checking every time. + HttpConnectionParams.setStaleCheckingEnabled(params, false); + + // Default connection and socket timeout of 20 seconds. Tweak to taste. + HttpConnectionParams.setConnectionTimeout(params, 20 * 1000); + HttpConnectionParams.setSoTimeout(params, 20 * 1000); + HttpConnectionParams.setSocketBufferSize(params, 8192); + + // Don't handle redirects -- return them to the caller. Our code + // often wants to re-POST after a redirect, which we must do ourselves. + HttpClientParams.setRedirecting(params, false); + + // Set the specified user agent and register standard protocols. + HttpProtocolParams.setUserAgent(params, userAgent); + SchemeRegistry schemeRegistry = new SchemeRegistry(); + schemeRegistry.register(new Scheme("http", + PlainSocketFactory.getSocketFactory(), 80)); + schemeRegistry.register(new Scheme("https", + socketFactoryWithCache(sessionCache), 443)); + + ClientConnectionManager manager = + new ThreadSafeClientConnManager(params, schemeRegistry); + + // We use a factory method to modify superclass initialization + // parameters without the funny call-a-static-method dance. + return new AndroidHttpClient(manager, params); + } + + /** + * Returns a socket factory backed by the given persistent session cache. + * + * @param sessionCache to retrieve sessions from, null for no cache + */ + private static SSLSocketFactory socketFactoryWithCache( + SSLClientSessionCache sessionCache) { + if (sessionCache == null) { + // Use the default factory which doesn't support persistent + // caching. + return SSLSocketFactory.getSocketFactory(); + } + + // Create a new SSL context backed by the cache. + // TODO: Keep a weak *identity* hash map of caches to engines. In the + // mean time, if we have two engines for the same cache, they'll still + // share sessions but will have to do so through the persistent cache. + SSLContextImpl sslContext = new SSLContextImpl(); + try { + sslContext.engineInit(null, null, null, sessionCache, null); + } catch (KeyManagementException e) { + throw new AssertionError(e); + } + return new SSLSocketFactory(sslContext.engineGetSocketFactory()); + } + + /** + * Create a new HttpClient with reasonable defaults (which you can update). + * @param userAgent to report in your HTTP requests. + * @return AndroidHttpClient for you to use for all your requests. + */ + public static AndroidHttpClient newInstance(String userAgent) { + return newInstance(userAgent, null /* session cache */); + } + + private final HttpClient delegate; + + private RuntimeException mLeakedException = new IllegalStateException( + "AndroidHttpClient created and never closed"); + + private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) { + this.delegate = new DefaultHttpClient(ccm, params) { + @Override + protected BasicHttpProcessor createHttpProcessor() { + // Add interceptor to prevent making requests from main thread. + BasicHttpProcessor processor = super.createHttpProcessor(); + processor.addRequestInterceptor(sThreadCheckInterceptor); + processor.addRequestInterceptor(new CurlLogger()); + + return processor; + } + + @Override + protected HttpContext createHttpContext() { + // Same as DefaultHttpClient.createHttpContext() minus the + // cookie store. + HttpContext context = new BasicHttpContext(); + context.setAttribute( + ClientContext.AUTHSCHEME_REGISTRY, + getAuthSchemes()); + context.setAttribute( + ClientContext.COOKIESPEC_REGISTRY, + getCookieSpecs()); + context.setAttribute( + ClientContext.CREDS_PROVIDER, + getCredentialsProvider()); + return context; + } + }; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (mLeakedException != null) { + Log.e(TAG, "Leak found", mLeakedException); + mLeakedException = null; + } + } + + /** + * Modifies a request to indicate to the server that we would like a + * gzipped response. (Uses the "Accept-Encoding" HTTP header.) + * @param request the request to modify + * @see #getUngzippedContent + */ + public static void modifyRequestToAcceptGzipResponse(HttpRequest request) { + request.addHeader("Accept-Encoding", "gzip"); + } + + /** + * Gets the input stream from a response entity. If the entity is gzipped + * then this will get a stream over the uncompressed data. + * + * @param entity the entity whose content should be read + * @return the input stream to read from + * @throws IOException + */ + public static InputStream getUngzippedContent(HttpEntity entity) + throws IOException { + InputStream responseStream = entity.getContent(); + if (responseStream == null) return responseStream; + Header header = entity.getContentEncoding(); + if (header == null) return responseStream; + String contentEncoding = header.getValue(); + if (contentEncoding == null) return responseStream; + if (contentEncoding.contains("gzip")) responseStream + = new GZIPInputStream(responseStream); + return responseStream; + } + + /** + * Release resources associated with this client. You must call this, + * or significant resources (sockets and memory) may be leaked. + */ + public void close() { + if (mLeakedException != null) { + getConnectionManager().shutdown(); + mLeakedException = null; + } + } + + public HttpParams getParams() { + return delegate.getParams(); + } + + public ClientConnectionManager getConnectionManager() { + return delegate.getConnectionManager(); + } + + public HttpResponse execute(HttpUriRequest request) throws IOException { + return delegate.execute(request); + } + + public HttpResponse execute(HttpUriRequest request, HttpContext context) + throws IOException { + return delegate.execute(request, context); + } + + public HttpResponse execute(HttpHost target, HttpRequest request) + throws IOException { + return delegate.execute(target, request); + } + + public HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) throws IOException { + return delegate.execute(target, request, context); + } + + public T execute(HttpUriRequest request, + ResponseHandler responseHandler) + throws IOException, ClientProtocolException { + return delegate.execute(request, responseHandler); + } + + public T execute(HttpUriRequest request, + ResponseHandler responseHandler, HttpContext context) + throws IOException, ClientProtocolException { + return delegate.execute(request, responseHandler, context); + } + + public T execute(HttpHost target, HttpRequest request, + ResponseHandler responseHandler) throws IOException, + ClientProtocolException { + return delegate.execute(target, request, responseHandler); + } + + public T execute(HttpHost target, HttpRequest request, + ResponseHandler responseHandler, HttpContext context) + throws IOException, ClientProtocolException { + return delegate.execute(target, request, responseHandler, context); + } + + /** + * Compress data to send to server. + * Creates a Http Entity holding the gzipped data. + * The data will not be compressed if it is too short. + * @param data The bytes to compress + * @return Entity holding the data + */ + public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver) + throws IOException { + AbstractHttpEntity entity; + if (data.length < getMinGzipSize(resolver)) { + entity = new ByteArrayEntity(data); + } else { + ByteArrayOutputStream arr = new ByteArrayOutputStream(); + OutputStream zipper = new GZIPOutputStream(arr); + zipper.write(data); + zipper.close(); + entity = new ByteArrayEntity(arr.toByteArray()); + entity.setContentEncoding("gzip"); + } + return entity; + } + + /** + * Retrieves the minimum size for compressing data. + * Shorter data will not be compressed. + */ + public static long getMinGzipSize(ContentResolver resolver) { + String sMinGzipBytes = Settings.Gservices.getString(resolver, + Settings.Gservices.SYNC_MIN_GZIP_BYTES); + + if (!TextUtils.isEmpty(sMinGzipBytes)) { + try { + return Long.parseLong(sMinGzipBytes); + } catch (NumberFormatException nfe) { + Log.w(TAG, "Unable to parse " + + Settings.Gservices.SYNC_MIN_GZIP_BYTES + " " + + sMinGzipBytes, nfe); + } + } + return DEFAULT_SYNC_MIN_GZIP_BYTES; + } + + /* cURL logging support. */ + + /** + * Logging tag and level. + */ + private static class LoggingConfiguration { + + private final String tag; + private final int level; + + private LoggingConfiguration(String tag, int level) { + this.tag = tag; + this.level = level; + } + + /** + * Returns true if logging is turned on for this configuration. + */ + private boolean isLoggable() { + return Log.isLoggable(tag, level); + } + + /** + * Returns true if auth logging is turned on for this configuration. Can only be set on + * insecure devices. + */ + private boolean isAuthLoggable() { + String secure = SystemProperties.get("ro.secure"); + return "0".equals(secure) && Log.isLoggable(tag + "-auth", level); + } + + /** + * Prints a message using this configuration. + */ + private void println(String message) { + Log.println(level, tag, message); + } + } + + /** cURL logging configuration. */ + private volatile LoggingConfiguration curlConfiguration; + + /** + * Enables cURL request logging for this client. + * + * @param name to log messages with + * @param level at which to log messages (see {@link android.util.Log}) + */ + public void enableCurlLogging(String name, int level) { + if (name == null) { + throw new NullPointerException("name"); + } + if (level < Log.VERBOSE || level > Log.ASSERT) { + throw new IllegalArgumentException("Level is out of range [" + + Log.VERBOSE + ".." + Log.ASSERT + "]"); + } + + curlConfiguration = new LoggingConfiguration(name, level); + } + + /** + * Disables cURL logging for this client. + */ + public void disableCurlLogging() { + curlConfiguration = null; + } + + /** + * Logs cURL commands equivalent to requests. + */ + private class CurlLogger implements HttpRequestInterceptor { + public void process(HttpRequest request, HttpContext context) + throws HttpException, IOException { + LoggingConfiguration configuration = curlConfiguration; + if (configuration != null + && configuration.isLoggable() + && request instanceof HttpUriRequest) { + configuration.println(toCurl((HttpUriRequest) request, + configuration.isAuthLoggable())); + } + } + } + + /** + * Generates a cURL command equivalent to the given request. + */ + private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException { + StringBuilder builder = new StringBuilder(); + + builder.append("curl "); + + for (Header header: request.getAllHeaders()) { + if (!logAuthToken + && (header.getName().equals("Authorization") || + header.getName().equals("Cookie"))) { + continue; + } + builder.append("--header \""); + builder.append(header.toString().trim()); + builder.append("\" "); + } + + URI uri = request.getURI(); + + // If this is a wrapped request, use the URI from the original + // request instead. getURI() on the wrapper seems to return a + // relative URI. We want an absolute URI. + if (request instanceof RequestWrapper) { + HttpRequest original = ((RequestWrapper) request).getOriginal(); + if (original instanceof HttpUriRequest) { + uri = ((HttpUriRequest) original).getURI(); + } + } + + builder.append("\""); + builder.append(uri); + builder.append("\""); + + if (request instanceof HttpEntityEnclosingRequest) { + HttpEntityEnclosingRequest entityRequest = + (HttpEntityEnclosingRequest) request; + HttpEntity entity = entityRequest.getEntity(); + if (entity != null && entity.isRepeatable()) { + if (entity.getContentLength() < 1024) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + entity.writeTo(stream); + String entityString = stream.toString(); + + // TODO: Check the content type, too. + builder.append(" --data-ascii \"") + .append(entityString) + .append("\""); + } else { + builder.append(" [TOO MUCH DATA TO INCLUDE]"); + } + } + } + + return builder.toString(); + } +} diff --git a/common/java/com/android/common/Rfc822InputFilter.java b/common/java/com/android/common/Rfc822InputFilter.java new file mode 100644 index 0000000..6dfdc7b --- /dev/null +++ b/common/java/com/android/common/Rfc822InputFilter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 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.common; + +import android.text.InputFilter; +import android.text.Spanned; +import android.text.SpannableStringBuilder; + +/** + * Implements special address cleanup rules: + * The first space key entry following an "@" symbol that is followed by any combination + * of letters and symbols, including one+ dots and zero commas, should insert an extra + * comma (followed by the space). + * + * @hide + */ +public class Rfc822InputFilter implements InputFilter { + + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, + int dstart, int dend) { + + // quick check - did they enter a single space? + if (end-start != 1 || source.charAt(start) != ' ') { + return null; + } + + // determine if the characters before the new space fit the pattern + // follow backwards and see if we find a comma, dot, or @ + int scanBack = dstart; + boolean dotFound = false; + while (scanBack > 0) { + char c = dest.charAt(--scanBack); + switch (c) { + case '.': + dotFound = true; // one or more dots are req'd + break; + case ',': + return null; + case '@': + if (!dotFound) { + return null; + } + // we have found a comma-insert case. now just do it + // in the least expensive way we can. + if (source instanceof Spanned) { + SpannableStringBuilder sb = new SpannableStringBuilder(","); + sb.append(source); + return sb; + } else { + return ", "; + } + default: + // just keep going + } + } + + // no termination cases were found, so don't edit the input + return null; + } +} diff --git a/common/java/com/android/common/Rfc822Validator.java b/common/java/com/android/common/Rfc822Validator.java new file mode 100644 index 0000000..087e425 --- /dev/null +++ b/common/java/com/android/common/Rfc822Validator.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2008 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.common; + +import android.text.TextUtils; +import android.text.util.Rfc822Token; +import android.text.util.Rfc822Tokenizer; +import android.widget.AutoCompleteTextView; + +import java.util.regex.Pattern; + +/** + * This class works as a Validator for AutoCompleteTextView for + * email addresses. If a token does not appear to be a valid address, + * it is trimmed of characters that cannot legitimately appear in one + * and has the specified domain name added. It is meant for use with + * {@link Rfc822Token} and {@link Rfc822Tokenizer}. + * + * @deprecated In the future make sure we don't quietly alter the user's + * text in ways they did not intend. Meanwhile, hide this + * class from the public API because it does not even have + * a full understanding of the syntax it claims to correct. + * @hide + */ +public class Rfc822Validator implements AutoCompleteTextView.Validator { + /* + * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we + * want to make sure we will keep accepting email addresses with TLD's + * that don't exist at the time of this writing, so this regexp relaxes + * that constraint by accepting any kind of top level domain, not just + * ".com", ".fr", etc... + */ + private static final Pattern EMAIL_ADDRESS_PATTERN = + Pattern.compile("[^\\s@]+@[^\\s@]+\\.[a-zA-z][a-zA-Z][a-zA-Z]*"); + + private String mDomain; + + /** + * Constructs a new validator that uses the specified domain name as + * the default when none is specified. + */ + public Rfc822Validator(String domain) { + mDomain = domain; + } + + /** + * {@inheritDoc} + */ + public boolean isValid(CharSequence text) { + Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text); + + return tokens.length == 1 && + EMAIL_ADDRESS_PATTERN. + matcher(tokens[0].getAddress()).matches(); + } + + /** + * @return a string in which all the characters that are illegal for the username + * or the domain name part of the email address have been removed. + */ + private String removeIllegalCharacters(String s) { + StringBuilder result = new StringBuilder(); + int length = s.length(); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + + /* + * An RFC822 atom can contain any ASCII printing character + * except for periods and any of the following punctuation. + * A local-part can contain multiple atoms, concatenated by + * periods, so do allow periods here. + */ + + if (c <= ' ' || c > '~') { + continue; + } + + if (c == '(' || c == ')' || c == '<' || c == '>' || + c == '@' || c == ',' || c == ';' || c == ':' || + c == '\\' || c == '"' || c == '[' || c == ']') { + continue; + } + + result.append(c); + } + return result.toString(); + } + + /** + * {@inheritDoc} + */ + public CharSequence fixText(CharSequence cs) { + // Return an empty string if the email address only contains spaces, \n or \t + if (TextUtils.getTrimmedLength(cs) == 0) return ""; + + Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < tokens.length; i++) { + String text = tokens[i].getAddress(); + int index = text.indexOf('@'); + if (index < 0) { + // If there is no @, just append the domain of the account + tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain); + } else { + // Otherwise, remove the illegal characters on both sides of the '@' + String fix = removeIllegalCharacters(text.substring(0, index)); + String domain = removeIllegalCharacters(text.substring(index + 1)); + tokens[i].setAddress(fix + "@" + (domain.length() != 0 ? domain : mDomain)); + } + + sb.append(tokens[i].toString()); + if (i + 1 < tokens.length) { + sb.append(", "); + } + } + + return sb; + } +} diff --git a/common/java/com/android/common/widget/NumberPicker.java b/common/java/com/android/common/widget/NumberPicker.java new file mode 100644 index 0000000..64b436f --- /dev/null +++ b/common/java/com/android/common/widget/NumberPicker.java @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2008 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.common.widget; + +import android.content.Context; +import android.os.Handler; +import android.text.InputFilter; +import android.text.InputType; +import android.text.Spanned; +import android.text.method.NumberKeyListener; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.view.View.OnLongClickListener; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.EditText; + +import com.android.internal.R; + +public class NumberPicker extends LinearLayout implements OnClickListener, + OnFocusChangeListener, OnLongClickListener { + + public interface OnChangedListener { + void onChanged(NumberPicker picker, int oldVal, int newVal); + } + + public interface Formatter { + String toString(int value); + } + + /* + * Use a custom NumberPicker formatting callback to use two-digit + * minutes strings like "01". Keeping a static formatter etc. is the + * most efficient way to do this; it avoids creating temporary objects + * on every call to format(). + */ + public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = + new NumberPicker.Formatter() { + final StringBuilder mBuilder = new StringBuilder(); + final java.util.Formatter mFmt = new java.util.Formatter(mBuilder); + final Object[] mArgs = new Object[1]; + public String toString(int value) { + mArgs[0] = value; + mBuilder.delete(0, mBuilder.length()); + mFmt.format("%02d", mArgs); + return mFmt.toString(); + } + }; + + private final Handler mHandler; + private final Runnable mRunnable = new Runnable() { + public void run() { + if (mIncrement) { + changeCurrent(mCurrent + 1); + mHandler.postDelayed(this, mSpeed); + } else if (mDecrement) { + changeCurrent(mCurrent - 1); + mHandler.postDelayed(this, mSpeed); + } + } + }; + + private final EditText mText; + private final InputFilter mNumberInputFilter; + + private String[] mDisplayedValues; + protected int mStart; + protected int mEnd; + protected int mCurrent; + protected int mPrevious; + private OnChangedListener mListener; + private Formatter mFormatter; + private long mSpeed = 300; + + private boolean mIncrement; + private boolean mDecrement; + + public NumberPicker(Context context) { + this(context, null); + } + + public NumberPicker(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @SuppressWarnings({"UnusedDeclaration"}) + public NumberPicker(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs); + setOrientation(VERTICAL); + LayoutInflater inflater = + (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.number_picker, this, true); + mHandler = new Handler(); + InputFilter inputFilter = new NumberPickerInputFilter(); + mNumberInputFilter = new NumberRangeKeyListener(); + mIncrementButton = (NumberPickerButton) findViewById(R.id.increment); + mIncrementButton.setOnClickListener(this); + mIncrementButton.setOnLongClickListener(this); + mIncrementButton.setNumberPicker(this); + mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement); + mDecrementButton.setOnClickListener(this); + mDecrementButton.setOnLongClickListener(this); + mDecrementButton.setNumberPicker(this); + + mText = (EditText) findViewById(R.id.timepicker_input); + mText.setOnFocusChangeListener(this); + mText.setFilters(new InputFilter[] {inputFilter}); + mText.setRawInputType(InputType.TYPE_CLASS_NUMBER); + + if (!isEnabled()) { + setEnabled(false); + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + mIncrementButton.setEnabled(enabled); + mDecrementButton.setEnabled(enabled); + mText.setEnabled(enabled); + } + + public void setOnChangeListener(OnChangedListener listener) { + mListener = listener; + } + + public void setFormatter(Formatter formatter) { + mFormatter = formatter; + } + + /** + * Set the range of numbers allowed for the number picker. The current + * value will be automatically set to the start. + * + * @param start the start of the range (inclusive) + * @param end the end of the range (inclusive) + */ + public void setRange(int start, int end) { + mStart = start; + mEnd = end; + mCurrent = start; + updateView(); + } + + /** + * Set the range of numbers allowed for the number picker. The current + * value will be automatically set to the start. Also provide a mapping + * for values used to display to the user. + * + * @param start the start of the range (inclusive) + * @param end the end of the range (inclusive) + * @param displayedValues the values displayed to the user. + */ + public void setRange(int start, int end, String[] displayedValues) { + mDisplayedValues = displayedValues; + mStart = start; + mEnd = end; + mCurrent = start; + updateView(); + } + + public void setCurrent(int current) { + mCurrent = current; + updateView(); + } + + /** + * The speed (in milliseconds) at which the numbers will scroll + * when the the +/- buttons are longpressed. Default is 300ms. + */ + public void setSpeed(long speed) { + mSpeed = speed; + } + + public void onClick(View v) { + validateInput(mText); + if (!mText.hasFocus()) mText.requestFocus(); + + // now perform the increment/decrement + if (R.id.increment == v.getId()) { + changeCurrent(mCurrent + 1); + } else if (R.id.decrement == v.getId()) { + changeCurrent(mCurrent - 1); + } + } + + private String formatNumber(int value) { + return (mFormatter != null) + ? mFormatter.toString(value) + : String.valueOf(value); + } + + protected void changeCurrent(int current) { + + // Wrap around the values if we go past the start or end + if (current > mEnd) { + current = mStart; + } else if (current < mStart) { + current = mEnd; + } + mPrevious = mCurrent; + mCurrent = current; + notifyChange(); + updateView(); + } + + protected void notifyChange() { + if (mListener != null) { + mListener.onChanged(this, mPrevious, mCurrent); + } + } + + protected void updateView() { + + /* If we don't have displayed values then use the + * current number else find the correct value in the + * displayed values for the current number. + */ + if (mDisplayedValues == null) { + mText.setText(formatNumber(mCurrent)); + } else { + mText.setText(mDisplayedValues[mCurrent - mStart]); + } + mText.setSelection(mText.getText().length()); + } + + private void validateCurrentView(CharSequence str) { + int val = getSelectedPos(str.toString()); + if ((val >= mStart) && (val <= mEnd)) { + if (mCurrent != val) { + mPrevious = mCurrent; + mCurrent = val; + notifyChange(); + } + } + updateView(); + } + + public void onFocusChange(View v, boolean hasFocus) { + + /* When focus is lost check that the text field + * has valid values. + */ + if (!hasFocus) { + validateInput(v); + } + } + + private void validateInput(View v) { + String str = String.valueOf(((TextView) v).getText()); + if ("".equals(str)) { + + // Restore to the old value as we don't allow empty values + updateView(); + } else { + + // Check the new value and ensure it's in range + validateCurrentView(str); + } + } + + /** + * We start the long click here but rely on the {@link NumberPickerButton} + * to inform us when the long click has ended. + */ + public boolean onLongClick(View v) { + + /* The text view may still have focus so clear it's focus which will + * trigger the on focus changed and any typed values to be pulled. + */ + mText.clearFocus(); + + if (R.id.increment == v.getId()) { + mIncrement = true; + mHandler.post(mRunnable); + } else if (R.id.decrement == v.getId()) { + mDecrement = true; + mHandler.post(mRunnable); + } + return true; + } + + public void cancelIncrement() { + mIncrement = false; + } + + public void cancelDecrement() { + mDecrement = false; + } + + private static final char[] DIGIT_CHARACTERS = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' + }; + + private NumberPickerButton mIncrementButton; + private NumberPickerButton mDecrementButton; + + private class NumberPickerInputFilter implements InputFilter { + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + if (mDisplayedValues == null) { + return mNumberInputFilter.filter(source, start, end, dest, dstart, dend); + } + CharSequence filtered = String.valueOf(source.subSequence(start, end)); + String result = String.valueOf(dest.subSequence(0, dstart)) + + filtered + + dest.subSequence(dend, dest.length()); + String str = String.valueOf(result).toLowerCase(); + for (String val : mDisplayedValues) { + val = val.toLowerCase(); + if (val.startsWith(str)) { + return filtered; + } + } + return ""; + } + } + + private class NumberRangeKeyListener extends NumberKeyListener { + + // XXX This doesn't allow for range limits when controlled by a + // soft input method! + public int getInputType() { + return InputType.TYPE_CLASS_NUMBER; + } + + @Override + protected char[] getAcceptedChars() { + return DIGIT_CHARACTERS; + } + + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + + CharSequence filtered = super.filter(source, start, end, dest, dstart, dend); + if (filtered == null) { + filtered = source.subSequence(start, end); + } + + String result = String.valueOf(dest.subSequence(0, dstart)) + + filtered + + dest.subSequence(dend, dest.length()); + + if ("".equals(result)) { + return result; + } + int val = getSelectedPos(result); + + /* Ensure the user can't type in a value greater + * than the max allowed. We have to allow less than min + * as the user might want to delete some numbers + * and then type a new number. + */ + if (val > mEnd) { + return ""; + } else { + return filtered; + } + } + } + + private int getSelectedPos(String str) { + if (mDisplayedValues == null) { + return Integer.parseInt(str); + } else { + for (int i = 0; i < mDisplayedValues.length; i++) { + + /* Don't force the user to type in jan when ja will do */ + str = str.toLowerCase(); + if (mDisplayedValues[i].toLowerCase().startsWith(str)) { + return mStart + i; + } + } + + /* The user might have typed in a number into the month field i.e. + * 10 instead of OCT so support that too. + */ + try { + return Integer.parseInt(str); + } catch (NumberFormatException e) { + + /* Ignore as if it's not a number we don't care */ + } + } + return mStart; + } + + /** + * @return the current value. + */ + public int getCurrent() { + return mCurrent; + } +} diff --git a/common/java/com/android/common/widget/NumberPickerButton.java b/common/java/com/android/common/widget/NumberPickerButton.java new file mode 100644 index 0000000..f6b6d5d --- /dev/null +++ b/common/java/com/android/common/widget/NumberPickerButton.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2008 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.common.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.widget.ImageButton; + +import com.android.internal.R; + +/** + * This class exists purely to cancel long click events. + */ +public class NumberPickerButton extends ImageButton { + + private NumberPicker mNumberPicker; + + public NumberPickerButton(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public NumberPickerButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NumberPickerButton(Context context) { + super(context); + } + + public void setNumberPicker(NumberPicker picker) { + mNumberPicker = picker; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + cancelLongpressIfRequired(event); + return super.onTouchEvent(event); + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + cancelLongpressIfRequired(event); + return super.onTrackballEvent(event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER) + || (keyCode == KeyEvent.KEYCODE_ENTER)) { + cancelLongpress(); + } + return super.onKeyUp(keyCode, event); + } + + private void cancelLongpressIfRequired(MotionEvent event) { + if ((event.getAction() == MotionEvent.ACTION_CANCEL) + || (event.getAction() == MotionEvent.ACTION_UP)) { + cancelLongpress(); + } + } + + private void cancelLongpress() { + if (R.id.increment == getId()) { + mNumberPicker.cancelIncrement(); + } else if (R.id.decrement == getId()) { + mNumberPicker.cancelDecrement(); + } + } +} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 909620d..10fef0d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -40,7 +40,6 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.net.http.AndroidHttpClient; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -4303,7 +4302,6 @@ public final class ActivityThread { private final void attach(boolean system) { sThreadLocal.set(this); mSystemThread = system; - AndroidHttpClient.setThreadBlocked(true); if (!system) { android.ddm.DdmHandleAppName.setAppName(""); RuntimeInit.setApplicationObject(mAppThread.asBinder()); @@ -4333,7 +4331,6 @@ public final class ActivityThread { private final void detach() { - AndroidHttpClient.setThreadBlocked(false); sThreadLocal.set(null); } diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java deleted file mode 100644 index c2013d5..0000000 --- a/core/java/android/net/http/AndroidHttpClient.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2007 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 android.net.http; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpException; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.HttpResponse; -import org.apache.http.entity.AbstractHttpEntity; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.client.HttpClient; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.protocol.ClientContext; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.params.HttpClientParams; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.client.RequestWrapper; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.protocol.BasicHttpProcessor; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; -import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; -import java.net.URI; -import java.security.KeyManagementException; - -import android.util.Log; -import android.content.ContentResolver; -import android.provider.Settings; -import android.text.TextUtils; -import android.os.SystemProperties; - -/** - * Subclass of the Apache {@link DefaultHttpClient} that is configured with - * reasonable default settings and registered schemes for Android, and - * also lets the user add {@link HttpRequestInterceptor} classes. - * Don't create this directly, use the {@link #newInstance} factory method. - * - *

This client processes cookies but does not retain them by default. - * To retain cookies, simply add a cookie store to the HttpContext:

- * - *
context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
- * - * {@hide} - */ -public final class AndroidHttpClient implements HttpClient { - - // Gzip of data shorter than this probably won't be worthwhile - public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256; - - private static final String TAG = "AndroidHttpClient"; - - - /** Set if HTTP requests are blocked from being executed on this thread */ - private static final ThreadLocal sThreadBlocked = - new ThreadLocal(); - - /** Interceptor throws an exception if the executing thread is blocked */ - private static final HttpRequestInterceptor sThreadCheckInterceptor = - new HttpRequestInterceptor() { - public void process(HttpRequest request, HttpContext context) { - if (sThreadBlocked.get() != null && sThreadBlocked.get()) { - throw new RuntimeException("This thread forbids HTTP requests"); - } - } - }; - - /** - * Create a new HttpClient with reasonable defaults (which you can update). - * - * @param userAgent to report in your HTTP requests. - * @param sessionCache persistent session cache - * @return AndroidHttpClient for you to use for all your requests. - */ - public static AndroidHttpClient newInstance(String userAgent, - SSLClientSessionCache sessionCache) { - HttpParams params = new BasicHttpParams(); - - // Turn off stale checking. Our connections break all the time anyway, - // and it's not worth it to pay the penalty of checking every time. - HttpConnectionParams.setStaleCheckingEnabled(params, false); - - // Default connection and socket timeout of 20 seconds. Tweak to taste. - HttpConnectionParams.setConnectionTimeout(params, 20 * 1000); - HttpConnectionParams.setSoTimeout(params, 20 * 1000); - HttpConnectionParams.setSocketBufferSize(params, 8192); - - // Don't handle redirects -- return them to the caller. Our code - // often wants to re-POST after a redirect, which we must do ourselves. - HttpClientParams.setRedirecting(params, false); - - // Set the specified user agent and register standard protocols. - HttpProtocolParams.setUserAgent(params, userAgent); - SchemeRegistry schemeRegistry = new SchemeRegistry(); - schemeRegistry.register(new Scheme("http", - PlainSocketFactory.getSocketFactory(), 80)); - schemeRegistry.register(new Scheme("https", - socketFactoryWithCache(sessionCache), 443)); - - ClientConnectionManager manager = - new ThreadSafeClientConnManager(params, schemeRegistry); - - // We use a factory method to modify superclass initialization - // parameters without the funny call-a-static-method dance. - return new AndroidHttpClient(manager, params); - } - - /** - * Returns a socket factory backed by the given persistent session cache. - * - * @param sessionCache to retrieve sessions from, null for no cache - */ - private static SSLSocketFactory socketFactoryWithCache( - SSLClientSessionCache sessionCache) { - if (sessionCache == null) { - // Use the default factory which doesn't support persistent - // caching. - return SSLSocketFactory.getSocketFactory(); - } - - // Create a new SSL context backed by the cache. - // TODO: Keep a weak *identity* hash map of caches to engines. In the - // mean time, if we have two engines for the same cache, they'll still - // share sessions but will have to do so through the persistent cache. - SSLContextImpl sslContext = new SSLContextImpl(); - try { - sslContext.engineInit(null, null, null, sessionCache, null); - } catch (KeyManagementException e) { - throw new AssertionError(e); - } - return new SSLSocketFactory(sslContext.engineGetSocketFactory()); - } - - /** - * Create a new HttpClient with reasonable defaults (which you can update). - * @param userAgent to report in your HTTP requests. - * @return AndroidHttpClient for you to use for all your requests. - */ - public static AndroidHttpClient newInstance(String userAgent) { - return newInstance(userAgent, null /* session cache */); - } - - private final HttpClient delegate; - - private RuntimeException mLeakedException = new IllegalStateException( - "AndroidHttpClient created and never closed"); - - private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) { - this.delegate = new DefaultHttpClient(ccm, params) { - @Override - protected BasicHttpProcessor createHttpProcessor() { - // Add interceptor to prevent making requests from main thread. - BasicHttpProcessor processor = super.createHttpProcessor(); - processor.addRequestInterceptor(sThreadCheckInterceptor); - processor.addRequestInterceptor(new CurlLogger()); - - return processor; - } - - @Override - protected HttpContext createHttpContext() { - // Same as DefaultHttpClient.createHttpContext() minus the - // cookie store. - HttpContext context = new BasicHttpContext(); - context.setAttribute( - ClientContext.AUTHSCHEME_REGISTRY, - getAuthSchemes()); - context.setAttribute( - ClientContext.COOKIESPEC_REGISTRY, - getCookieSpecs()); - context.setAttribute( - ClientContext.CREDS_PROVIDER, - getCredentialsProvider()); - return context; - } - }; - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - if (mLeakedException != null) { - Log.e(TAG, "Leak found", mLeakedException); - mLeakedException = null; - } - } - - /** - * Block this thread from executing HTTP requests. - * Used to guard against HTTP requests blocking the main application thread. - * @param blocked if HTTP requests run on this thread should be denied - */ - public static void setThreadBlocked(boolean blocked) { - sThreadBlocked.set(blocked); - } - - /** - * Modifies a request to indicate to the server that we would like a - * gzipped response. (Uses the "Accept-Encoding" HTTP header.) - * @param request the request to modify - * @see #getUngzippedContent - */ - public static void modifyRequestToAcceptGzipResponse(HttpRequest request) { - request.addHeader("Accept-Encoding", "gzip"); - } - - /** - * Gets the input stream from a response entity. If the entity is gzipped - * then this will get a stream over the uncompressed data. - * - * @param entity the entity whose content should be read - * @return the input stream to read from - * @throws IOException - */ - public static InputStream getUngzippedContent(HttpEntity entity) - throws IOException { - InputStream responseStream = entity.getContent(); - if (responseStream == null) return responseStream; - Header header = entity.getContentEncoding(); - if (header == null) return responseStream; - String contentEncoding = header.getValue(); - if (contentEncoding == null) return responseStream; - if (contentEncoding.contains("gzip")) responseStream - = new GZIPInputStream(responseStream); - return responseStream; - } - - /** - * Release resources associated with this client. You must call this, - * or significant resources (sockets and memory) may be leaked. - */ - public void close() { - if (mLeakedException != null) { - getConnectionManager().shutdown(); - mLeakedException = null; - } - } - - public HttpParams getParams() { - return delegate.getParams(); - } - - public ClientConnectionManager getConnectionManager() { - return delegate.getConnectionManager(); - } - - public HttpResponse execute(HttpUriRequest request) throws IOException { - return delegate.execute(request); - } - - public HttpResponse execute(HttpUriRequest request, HttpContext context) - throws IOException { - return delegate.execute(request, context); - } - - public HttpResponse execute(HttpHost target, HttpRequest request) - throws IOException { - return delegate.execute(target, request); - } - - public HttpResponse execute(HttpHost target, HttpRequest request, - HttpContext context) throws IOException { - return delegate.execute(target, request, context); - } - - public T execute(HttpUriRequest request, - ResponseHandler responseHandler) - throws IOException, ClientProtocolException { - return delegate.execute(request, responseHandler); - } - - public T execute(HttpUriRequest request, - ResponseHandler responseHandler, HttpContext context) - throws IOException, ClientProtocolException { - return delegate.execute(request, responseHandler, context); - } - - public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler) throws IOException, - ClientProtocolException { - return delegate.execute(target, request, responseHandler); - } - - public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler, HttpContext context) - throws IOException, ClientProtocolException { - return delegate.execute(target, request, responseHandler, context); - } - - /** - * Compress data to send to server. - * Creates a Http Entity holding the gzipped data. - * The data will not be compressed if it is too short. - * @param data The bytes to compress - * @return Entity holding the data - */ - public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver) - throws IOException { - AbstractHttpEntity entity; - if (data.length < getMinGzipSize(resolver)) { - entity = new ByteArrayEntity(data); - } else { - ByteArrayOutputStream arr = new ByteArrayOutputStream(); - OutputStream zipper = new GZIPOutputStream(arr); - zipper.write(data); - zipper.close(); - entity = new ByteArrayEntity(arr.toByteArray()); - entity.setContentEncoding("gzip"); - } - return entity; - } - - /** - * Retrieves the minimum size for compressing data. - * Shorter data will not be compressed. - */ - public static long getMinGzipSize(ContentResolver resolver) { - String sMinGzipBytes = Settings.Gservices.getString(resolver, - Settings.Gservices.SYNC_MIN_GZIP_BYTES); - - if (!TextUtils.isEmpty(sMinGzipBytes)) { - try { - return Long.parseLong(sMinGzipBytes); - } catch (NumberFormatException nfe) { - Log.w(TAG, "Unable to parse " + - Settings.Gservices.SYNC_MIN_GZIP_BYTES + " " + - sMinGzipBytes, nfe); - } - } - return DEFAULT_SYNC_MIN_GZIP_BYTES; - } - - /* cURL logging support. */ - - /** - * Logging tag and level. - */ - private static class LoggingConfiguration { - - private final String tag; - private final int level; - - private LoggingConfiguration(String tag, int level) { - this.tag = tag; - this.level = level; - } - - /** - * Returns true if logging is turned on for this configuration. - */ - private boolean isLoggable() { - return Log.isLoggable(tag, level); - } - - /** - * Returns true if auth logging is turned on for this configuration. Can only be set on - * insecure devices. - */ - private boolean isAuthLoggable() { - String secure = SystemProperties.get("ro.secure"); - return "0".equals(secure) && Log.isLoggable(tag + "-auth", level); - } - - /** - * Prints a message using this configuration. - */ - private void println(String message) { - Log.println(level, tag, message); - } - } - - /** cURL logging configuration. */ - private volatile LoggingConfiguration curlConfiguration; - - /** - * Enables cURL request logging for this client. - * - * @param name to log messages with - * @param level at which to log messages (see {@link android.util.Log}) - */ - public void enableCurlLogging(String name, int level) { - if (name == null) { - throw new NullPointerException("name"); - } - if (level < Log.VERBOSE || level > Log.ASSERT) { - throw new IllegalArgumentException("Level is out of range [" - + Log.VERBOSE + ".." + Log.ASSERT + "]"); - } - - curlConfiguration = new LoggingConfiguration(name, level); - } - - /** - * Disables cURL logging for this client. - */ - public void disableCurlLogging() { - curlConfiguration = null; - } - - /** - * Logs cURL commands equivalent to requests. - */ - private class CurlLogger implements HttpRequestInterceptor { - public void process(HttpRequest request, HttpContext context) - throws HttpException, IOException { - LoggingConfiguration configuration = curlConfiguration; - if (configuration != null - && configuration.isLoggable() - && request instanceof HttpUriRequest) { - configuration.println(toCurl((HttpUriRequest) request, - configuration.isAuthLoggable())); - } - } - } - - /** - * Generates a cURL command equivalent to the given request. - */ - private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException { - StringBuilder builder = new StringBuilder(); - - builder.append("curl "); - - for (Header header: request.getAllHeaders()) { - if (!logAuthToken - && (header.getName().equals("Authorization") || - header.getName().equals("Cookie"))) { - continue; - } - builder.append("--header \""); - builder.append(header.toString().trim()); - builder.append("\" "); - } - - URI uri = request.getURI(); - - // If this is a wrapped request, use the URI from the original - // request instead. getURI() on the wrapper seems to return a - // relative URI. We want an absolute URI. - if (request instanceof RequestWrapper) { - HttpRequest original = ((RequestWrapper) request).getOriginal(); - if (original instanceof HttpUriRequest) { - uri = ((HttpUriRequest) original).getURI(); - } - } - - builder.append("\""); - builder.append(uri); - builder.append("\""); - - if (request instanceof HttpEntityEnclosingRequest) { - HttpEntityEnclosingRequest entityRequest = - (HttpEntityEnclosingRequest) request; - HttpEntity entity = entityRequest.getEntity(); - if (entity != null && entity.isRepeatable()) { - if (entity.getContentLength() < 1024) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - entity.writeTo(stream); - String entityString = stream.toString(); - - // TODO: Check the content type, too. - builder.append(" --data-ascii \"") - .append(entityString) - .append("\""); - } else { - builder.append(" [TOO MUCH DATA TO INCLUDE]"); - } - } - } - - return builder.toString(); - } -} diff --git a/core/java/android/text/util/Rfc822InputFilter.java b/core/java/android/text/util/Rfc822InputFilter.java deleted file mode 100644 index 8c8b7fc..0000000 --- a/core/java/android/text/util/Rfc822InputFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -package android.text.util; - -import android.text.InputFilter; -import android.text.Spanned; -import android.text.SpannableStringBuilder; - -/** - * Implements special address cleanup rules: - * The first space key entry following an "@" symbol that is followed by any combination - * of letters and symbols, including one+ dots and zero commas, should insert an extra - * comma (followed by the space). - * - * @hide - */ -public class Rfc822InputFilter implements InputFilter { - - public CharSequence filter(CharSequence source, int start, int end, Spanned dest, - int dstart, int dend) { - - // quick check - did they enter a single space? - if (end-start != 1 || source.charAt(start) != ' ') { - return null; - } - - // determine if the characters before the new space fit the pattern - // follow backwards and see if we find a comma, dot, or @ - int scanBack = dstart; - boolean dotFound = false; - while (scanBack > 0) { - char c = dest.charAt(--scanBack); - switch (c) { - case '.': - dotFound = true; // one or more dots are req'd - break; - case ',': - return null; - case '@': - if (!dotFound) { - return null; - } - // we have found a comma-insert case. now just do it - // in the least expensive way we can. - if (source instanceof Spanned) { - SpannableStringBuilder sb = new SpannableStringBuilder(","); - sb.append(source); - return sb; - } else { - return ", "; - } - default: - // just keep going - } - } - - // no termination cases were found, so don't edit the input - return null; - } -} diff --git a/core/java/android/text/util/Rfc822Validator.java b/core/java/android/text/util/Rfc822Validator.java deleted file mode 100644 index 6a6bf69..0000000 --- a/core/java/android/text/util/Rfc822Validator.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2008 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 android.text.util; - -import android.text.TextUtils; -import android.widget.AutoCompleteTextView; - -import java.util.regex.Pattern; - -/** - * This class works as a Validator for AutoCompleteTextView for - * email addresses. If a token does not appear to be a valid address, - * it is trimmed of characters that cannot legitimately appear in one - * and has the specified domain name added. It is meant for use with - * {@link Rfc822Token} and {@link Rfc822Tokenizer}. - * - * @deprecated In the future make sure we don't quietly alter the user's - * text in ways they did not intend. Meanwhile, hide this - * class from the public API because it does not even have - * a full understanding of the syntax it claims to correct. - * @hide - */ -public class Rfc822Validator implements AutoCompleteTextView.Validator { - /* - * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we - * want to make sure we will keep accepting email addresses with TLD's - * that don't exist at the time of this writing, so this regexp relaxes - * that constraint by accepting any kind of top level domain, not just - * ".com", ".fr", etc... - */ - private static final Pattern EMAIL_ADDRESS_PATTERN = - Pattern.compile("[^\\s@]+@[^\\s@]+\\.[a-zA-z][a-zA-Z][a-zA-Z]*"); - - private String mDomain; - - /** - * Constructs a new validator that uses the specified domain name as - * the default when none is specified. - */ - public Rfc822Validator(String domain) { - mDomain = domain; - } - - /** - * {@inheritDoc} - */ - public boolean isValid(CharSequence text) { - Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text); - - return tokens.length == 1 && - EMAIL_ADDRESS_PATTERN. - matcher(tokens[0].getAddress()).matches(); - } - - /** - * @return a string in which all the characters that are illegal for the username - * or the domain name part of the email address have been removed. - */ - private String removeIllegalCharacters(String s) { - StringBuilder result = new StringBuilder(); - int length = s.length(); - for (int i = 0; i < length; i++) { - char c = s.charAt(i); - - /* - * An RFC822 atom can contain any ASCII printing character - * except for periods and any of the following punctuation. - * A local-part can contain multiple atoms, concatenated by - * periods, so do allow periods here. - */ - - if (c <= ' ' || c > '~') { - continue; - } - - if (c == '(' || c == ')' || c == '<' || c == '>' || - c == '@' || c == ',' || c == ';' || c == ':' || - c == '\\' || c == '"' || c == '[' || c == ']') { - continue; - } - - result.append(c); - } - return result.toString(); - } - - /** - * {@inheritDoc} - */ - public CharSequence fixText(CharSequence cs) { - // Return an empty string if the email address only contains spaces, \n or \t - if (TextUtils.getTrimmedLength(cs) == 0) return ""; - - Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < tokens.length; i++) { - String text = tokens[i].getAddress(); - int index = text.indexOf('@'); - if (index < 0) { - // If there is no @, just append the domain of the account - tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain); - } else { - // Otherwise, remove the illegal characters on both sides of the '@' - String fix = removeIllegalCharacters(text.substring(0, index)); - String domain = removeIllegalCharacters(text.substring(index + 1)); - tokens[i].setAddress(fix + "@" + (domain.length() != 0 ? domain : mDomain)); - } - - sb.append(tokens[i].toString()); - if (i + 1 < tokens.length) { - sb.append(", "); - } - } - - return sb; - } -} diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 5e76cc3..b657e8e 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -26,9 +26,9 @@ import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; +import com.android.common.widget.NumberPicker; +import com.android.common.widget.NumberPicker.OnChangedListener; import com.android.internal.R; -import com.android.internal.widget.NumberPicker; -import com.android.internal.widget.NumberPicker.OnChangedListener; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index ab4edc5..b87e278 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -25,7 +25,7 @@ import android.view.LayoutInflater; import android.view.View; import com.android.internal.R; -import com.android.internal.widget.NumberPicker; +import com.android.common.widget.NumberPicker; import java.text.DateFormatSymbols; import java.util.Calendar; @@ -357,4 +357,3 @@ public class TimePicker extends FrameLayout { mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute()); } } - diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java deleted file mode 100644 index ae08eca..0000000 --- a/core/java/com/android/internal/widget/NumberPicker.java +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright (C) 2008 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.internal.widget; - -import android.content.Context; -import android.os.Handler; -import android.text.InputFilter; -import android.text.InputType; -import android.text.Spanned; -import android.text.method.NumberKeyListener; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnFocusChangeListener; -import android.view.View.OnLongClickListener; -import android.widget.TextView; -import android.widget.LinearLayout; -import android.widget.EditText; - -import com.android.internal.R; - -public class NumberPicker extends LinearLayout implements OnClickListener, - OnFocusChangeListener, OnLongClickListener { - - public interface OnChangedListener { - void onChanged(NumberPicker picker, int oldVal, int newVal); - } - - public interface Formatter { - String toString(int value); - } - - /* - * Use a custom NumberPicker formatting callback to use two-digit - * minutes strings like "01". Keeping a static formatter etc. is the - * most efficient way to do this; it avoids creating temporary objects - * on every call to format(). - */ - public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = - new NumberPicker.Formatter() { - final StringBuilder mBuilder = new StringBuilder(); - final java.util.Formatter mFmt = new java.util.Formatter(mBuilder); - final Object[] mArgs = new Object[1]; - public String toString(int value) { - mArgs[0] = value; - mBuilder.delete(0, mBuilder.length()); - mFmt.format("%02d", mArgs); - return mFmt.toString(); - } - }; - - private final Handler mHandler; - private final Runnable mRunnable = new Runnable() { - public void run() { - if (mIncrement) { - changeCurrent(mCurrent + 1); - mHandler.postDelayed(this, mSpeed); - } else if (mDecrement) { - changeCurrent(mCurrent - 1); - mHandler.postDelayed(this, mSpeed); - } - } - }; - - private final EditText mText; - private final InputFilter mNumberInputFilter; - - private String[] mDisplayedValues; - protected int mStart; - protected int mEnd; - protected int mCurrent; - protected int mPrevious; - private OnChangedListener mListener; - private Formatter mFormatter; - private long mSpeed = 300; - - private boolean mIncrement; - private boolean mDecrement; - - public NumberPicker(Context context) { - this(context, null); - } - - public NumberPicker(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - @SuppressWarnings({"UnusedDeclaration"}) - public NumberPicker(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs); - setOrientation(VERTICAL); - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.number_picker, this, true); - mHandler = new Handler(); - InputFilter inputFilter = new NumberPickerInputFilter(); - mNumberInputFilter = new NumberRangeKeyListener(); - mIncrementButton = (NumberPickerButton) findViewById(R.id.increment); - mIncrementButton.setOnClickListener(this); - mIncrementButton.setOnLongClickListener(this); - mIncrementButton.setNumberPicker(this); - mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement); - mDecrementButton.setOnClickListener(this); - mDecrementButton.setOnLongClickListener(this); - mDecrementButton.setNumberPicker(this); - - mText = (EditText) findViewById(R.id.timepicker_input); - mText.setOnFocusChangeListener(this); - mText.setFilters(new InputFilter[] {inputFilter}); - mText.setRawInputType(InputType.TYPE_CLASS_NUMBER); - - if (!isEnabled()) { - setEnabled(false); - } - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - mIncrementButton.setEnabled(enabled); - mDecrementButton.setEnabled(enabled); - mText.setEnabled(enabled); - } - - public void setOnChangeListener(OnChangedListener listener) { - mListener = listener; - } - - public void setFormatter(Formatter formatter) { - mFormatter = formatter; - } - - /** - * Set the range of numbers allowed for the number picker. The current - * value will be automatically set to the start. - * - * @param start the start of the range (inclusive) - * @param end the end of the range (inclusive) - */ - public void setRange(int start, int end) { - mStart = start; - mEnd = end; - mCurrent = start; - updateView(); - } - - /** - * Set the range of numbers allowed for the number picker. The current - * value will be automatically set to the start. Also provide a mapping - * for values used to display to the user. - * - * @param start the start of the range (inclusive) - * @param end the end of the range (inclusive) - * @param displayedValues the values displayed to the user. - */ - public void setRange(int start, int end, String[] displayedValues) { - mDisplayedValues = displayedValues; - mStart = start; - mEnd = end; - mCurrent = start; - updateView(); - } - - public void setCurrent(int current) { - mCurrent = current; - updateView(); - } - - /** - * The speed (in milliseconds) at which the numbers will scroll - * when the the +/- buttons are longpressed. Default is 300ms. - */ - public void setSpeed(long speed) { - mSpeed = speed; - } - - public void onClick(View v) { - validateInput(mText); - if (!mText.hasFocus()) mText.requestFocus(); - - // now perform the increment/decrement - if (R.id.increment == v.getId()) { - changeCurrent(mCurrent + 1); - } else if (R.id.decrement == v.getId()) { - changeCurrent(mCurrent - 1); - } - } - - private String formatNumber(int value) { - return (mFormatter != null) - ? mFormatter.toString(value) - : String.valueOf(value); - } - - protected void changeCurrent(int current) { - - // Wrap around the values if we go past the start or end - if (current > mEnd) { - current = mStart; - } else if (current < mStart) { - current = mEnd; - } - mPrevious = mCurrent; - mCurrent = current; - notifyChange(); - updateView(); - } - - protected void notifyChange() { - if (mListener != null) { - mListener.onChanged(this, mPrevious, mCurrent); - } - } - - protected void updateView() { - - /* If we don't have displayed values then use the - * current number else find the correct value in the - * displayed values for the current number. - */ - if (mDisplayedValues == null) { - mText.setText(formatNumber(mCurrent)); - } else { - mText.setText(mDisplayedValues[mCurrent - mStart]); - } - mText.setSelection(mText.getText().length()); - } - - private void validateCurrentView(CharSequence str) { - int val = getSelectedPos(str.toString()); - if ((val >= mStart) && (val <= mEnd)) { - if (mCurrent != val) { - mPrevious = mCurrent; - mCurrent = val; - notifyChange(); - } - } - updateView(); - } - - public void onFocusChange(View v, boolean hasFocus) { - - /* When focus is lost check that the text field - * has valid values. - */ - if (!hasFocus) { - validateInput(v); - } - } - - private void validateInput(View v) { - String str = String.valueOf(((TextView) v).getText()); - if ("".equals(str)) { - - // Restore to the old value as we don't allow empty values - updateView(); - } else { - - // Check the new value and ensure it's in range - validateCurrentView(str); - } - } - - /** - * We start the long click here but rely on the {@link NumberPickerButton} - * to inform us when the long click has ended. - */ - public boolean onLongClick(View v) { - - /* The text view may still have focus so clear it's focus which will - * trigger the on focus changed and any typed values to be pulled. - */ - mText.clearFocus(); - - if (R.id.increment == v.getId()) { - mIncrement = true; - mHandler.post(mRunnable); - } else if (R.id.decrement == v.getId()) { - mDecrement = true; - mHandler.post(mRunnable); - } - return true; - } - - public void cancelIncrement() { - mIncrement = false; - } - - public void cancelDecrement() { - mDecrement = false; - } - - private static final char[] DIGIT_CHARACTERS = new char[] { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' - }; - - private NumberPickerButton mIncrementButton; - private NumberPickerButton mDecrementButton; - - private class NumberPickerInputFilter implements InputFilter { - public CharSequence filter(CharSequence source, int start, int end, - Spanned dest, int dstart, int dend) { - if (mDisplayedValues == null) { - return mNumberInputFilter.filter(source, start, end, dest, dstart, dend); - } - CharSequence filtered = String.valueOf(source.subSequence(start, end)); - String result = String.valueOf(dest.subSequence(0, dstart)) - + filtered - + dest.subSequence(dend, dest.length()); - String str = String.valueOf(result).toLowerCase(); - for (String val : mDisplayedValues) { - val = val.toLowerCase(); - if (val.startsWith(str)) { - return filtered; - } - } - return ""; - } - } - - private class NumberRangeKeyListener extends NumberKeyListener { - - // XXX This doesn't allow for range limits when controlled by a - // soft input method! - public int getInputType() { - return InputType.TYPE_CLASS_NUMBER; - } - - @Override - protected char[] getAcceptedChars() { - return DIGIT_CHARACTERS; - } - - @Override - public CharSequence filter(CharSequence source, int start, int end, - Spanned dest, int dstart, int dend) { - - CharSequence filtered = super.filter(source, start, end, dest, dstart, dend); - if (filtered == null) { - filtered = source.subSequence(start, end); - } - - String result = String.valueOf(dest.subSequence(0, dstart)) - + filtered - + dest.subSequence(dend, dest.length()); - - if ("".equals(result)) { - return result; - } - int val = getSelectedPos(result); - - /* Ensure the user can't type in a value greater - * than the max allowed. We have to allow less than min - * as the user might want to delete some numbers - * and then type a new number. - */ - if (val > mEnd) { - return ""; - } else { - return filtered; - } - } - } - - private int getSelectedPos(String str) { - if (mDisplayedValues == null) { - return Integer.parseInt(str); - } else { - for (int i = 0; i < mDisplayedValues.length; i++) { - - /* Don't force the user to type in jan when ja will do */ - str = str.toLowerCase(); - if (mDisplayedValues[i].toLowerCase().startsWith(str)) { - return mStart + i; - } - } - - /* The user might have typed in a number into the month field i.e. - * 10 instead of OCT so support that too. - */ - try { - return Integer.parseInt(str); - } catch (NumberFormatException e) { - - /* Ignore as if it's not a number we don't care */ - } - } - return mStart; - } - - /** - * @return the current value. - */ - public int getCurrent() { - return mCurrent; - } -} \ No newline at end of file diff --git a/core/java/com/android/internal/widget/NumberPickerButton.java b/core/java/com/android/internal/widget/NumberPickerButton.java deleted file mode 100644 index 39f1e2c..0000000 --- a/core/java/com/android/internal/widget/NumberPickerButton.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2008 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.internal.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.widget.ImageButton; - -import com.android.internal.R; - -/** - * This class exists purely to cancel long click events. - */ -public class NumberPickerButton extends ImageButton { - - private NumberPicker mNumberPicker; - - public NumberPickerButton(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - } - - public NumberPickerButton(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public NumberPickerButton(Context context) { - super(context); - } - - public void setNumberPicker(NumberPicker picker) { - mNumberPicker = picker; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - cancelLongpressIfRequired(event); - return super.onTouchEvent(event); - } - - @Override - public boolean onTrackballEvent(MotionEvent event) { - cancelLongpressIfRequired(event); - return super.onTrackballEvent(event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER) - || (keyCode == KeyEvent.KEYCODE_ENTER)) { - cancelLongpress(); - } - return super.onKeyUp(keyCode, event); - } - - private void cancelLongpressIfRequired(MotionEvent event) { - if ((event.getAction() == MotionEvent.ACTION_CANCEL) - || (event.getAction() == MotionEvent.ACTION_UP)) { - cancelLongpress(); - } - } - - private void cancelLongpress() { - if (R.id.increment == getId()) { - mNumberPicker.cancelIncrement(); - } else if (R.id.decrement == getId()) { - mNumberPicker.cancelDecrement(); - } - } -} diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java index 7bdc7f8..0337672 100644 --- a/core/java/com/google/android/net/GoogleHttpClient.java +++ b/core/java/com/google/android/net/GoogleHttpClient.java @@ -20,12 +20,12 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.net.TrafficStats; -import android.net.http.AndroidHttpClient; import android.os.Build; import android.os.SystemClock; import android.provider.Checkin; import android.util.Config; import android.util.Log; +import com.android.common.AndroidHttpClient; import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; diff --git a/location/java/com/android/internal/location/GpsXtraDownloader.java b/location/java/com/android/internal/location/GpsXtraDownloader.java index 33ebce7..02a9f48 100644 --- a/location/java/com/android/internal/location/GpsXtraDownloader.java +++ b/location/java/com/android/internal/location/GpsXtraDownloader.java @@ -32,10 +32,12 @@ import java.util.Random; import android.content.Context; import android.net.Proxy; -import android.net.http.AndroidHttpClient; import android.util.Config; import android.util.Log; +import com.android.common.AndroidHttpClient; + + /** * A class for downloading GPS XTRA data. * @@ -169,4 +171,3 @@ public class GpsXtraDownloader { } } - diff --git a/preloaded-classes b/preloaded-classes index 0410557..a50ccc8 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -441,10 +441,6 @@ android.net.Uri$Part android.net.Uri$PathSegments android.net.Uri$StringUri android.net.WebAddress -android.net.http.AndroidHttpClient -android.net.http.AndroidHttpClient$1 -android.net.http.AndroidHttpClient$2 -android.net.http.AndroidHttpClientConnection android.net.http.CertificateChainValidator android.net.http.Connection android.net.http.ConnectionThread @@ -718,7 +714,6 @@ android.text.util.Linkify android.text.util.Linkify$1 android.text.util.Linkify$4 android.text.util.Rfc822Tokenizer -android.text.util.Rfc822Validator android.util.AttributeSet android.util.DayOfMonthCursor android.util.DisplayMetrics @@ -1077,10 +1072,6 @@ com.android.internal.widget.ContactHeaderWidget com.android.internal.widget.DialogTitle com.android.internal.widget.EditableInputConnection com.android.internal.widget.LockPatternUtils -com.android.internal.widget.NumberPicker -com.android.internal.widget.NumberPicker$1 -com.android.internal.widget.NumberPicker$NumberRangeKeyListener -com.android.internal.widget.NumberPickerButton com.android.internal.widget.RotarySelector com.android.internal.widget.Smileys com.google.android.gles_jni.EGLDisplayImpl diff --git a/tests/AndroidTests/src/com/android/unit_tests/GoogleHttpClientTest.java b/tests/AndroidTests/src/com/android/unit_tests/GoogleHttpClientTest.java index d970de3..bf0245c 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/GoogleHttpClientTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/GoogleHttpClientTest.java @@ -17,7 +17,6 @@ package com.android.unit_tests; import android.content.ContentResolver; -import android.net.http.AndroidHttpClient; import android.provider.Checkin; import android.provider.Settings; import android.test.AndroidTestCase; @@ -64,6 +63,10 @@ public class GoogleHttpClientTest extends AndroidTestCase { if (mServer != null) mServer.shutdown(); } + // + // Fix this test to use the new mechanism to indicate that the + // Http client is running in the UI thread + // bug: http://b/2322326 @LargeTest public void testThreadCheck() throws Exception { ContentResolver resolver = getContext().getContentResolver(); @@ -77,7 +80,9 @@ public class GoogleHttpClientTest extends AndroidTestCase { // This is actually an AndroidHttpClient feature... // TODO: somehow test that Activity threads have the flag set? - AndroidHttpClient.setThreadBlocked(true); + // Thus now uses the looper state to determine if it is in a UI + // thread + //AndroidHttpClient.setThreadBlocked(true); try { client.execute(method); @@ -85,7 +90,7 @@ public class GoogleHttpClientTest extends AndroidTestCase { } catch (RuntimeException e) { if (!e.toString().contains("forbids HTTP requests")) throw e; } finally { - AndroidHttpClient.setThreadBlocked(false); + // AndroidHttpClient.setThreadBlocked(false); } HttpResponse response = client.execute(method); diff --git a/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java b/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java index 7720041..b194896 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java @@ -16,9 +16,6 @@ package com.android.unit_tests; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; - import android.graphics.Paint; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; @@ -29,9 +26,12 @@ import android.text.SpannedString; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.StyleSpan; -import android.text.util.Rfc822Validator; import android.test.MoreAsserts; +import com.android.common.Rfc822Validator; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + import junit.framework.TestCase; import java.util.List; diff --git a/tests/SslLoad/src/com/android/sslload/SslLoad.java b/tests/SslLoad/src/com/android/sslload/SslLoad.java index 9a08024..1470d48 100644 --- a/tests/SslLoad/src/com/android/sslload/SslLoad.java +++ b/tests/SslLoad/src/com/android/sslload/SslLoad.java @@ -34,8 +34,8 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; -import android.net.http.AndroidHttpClient; import android.util.Log; +import com.android.common.AndroidHttpClient; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; -- cgit v1.1