diff options
Diffstat (limited to 'core/java')
32 files changed, 2840 insertions, 26 deletions
diff --git a/core/java/android/app/AliasActivity.java b/core/java/android/app/AliasActivity.java index 7527a5b..3756529 100644 --- a/core/java/android/app/AliasActivity.java +++ b/core/java/android/app/AliasActivity.java @@ -26,7 +26,8 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import android.util.AttributeSet; import android.util.Xml; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; import java.io.IOException; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 50dcdf9..0e21936 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -17,7 +17,7 @@ package android.app; import com.android.internal.policy.PolicyManager; -import com.android.common.XmlUtils; +import com.android.internal.util.XmlUtils; import com.google.android.collect.Maps; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index cb6aab6..af68689 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -16,8 +16,6 @@ package android.app; -import com.android.common.Patterns; -import com.android.common.speech.Recognition; import static android.app.SuggestionsAdapter.getColumnString; @@ -48,6 +46,7 @@ import android.text.TextWatcher; import android.util.AndroidRuntimeException; import android.util.AttributeSet; import android.util.Log; +import android.util.Patterns; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.KeyEvent; @@ -820,7 +819,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt); voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language); voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults); - voiceIntent.putExtra(Recognition.EXTRA_CALLING_PACKAGE, + voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null : searchActivity.toShortString()); // Add the values that configure forwarding the results diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 607605d..fb3f646 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -34,7 +34,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; import java.io.IOException; import java.io.Serializable; diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 023c024..452fd8a 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -34,7 +34,8 @@ import android.util.AndroidException; import android.util.Config; import android.util.Log; import android.util.Printer; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; /** * Structured description of Intent values to be matched. An IntentFilter can diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index fcb910d..5aad3af 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -18,7 +18,7 @@ package android.content; import com.android.internal.os.AtomicFile; import com.android.internal.util.ArrayUtils; -import com.android.common.FastXmlSerializer; +import com.android.internal.util.FastXmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 98aacaa..6599ae5 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -35,7 +35,8 @@ import android.util.Config; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; import java.io.File; import java.io.IOException; diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index a885820..b74c073 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -43,7 +43,7 @@ import java.io.IOException; import java.io.FileInputStream; import com.android.internal.os.AtomicFile; -import com.android.common.FastXmlSerializer; +import com.android.internal.util.FastXmlSerializer; import com.google.android.collect.Maps; import com.google.android.collect.Lists; diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index a5e39d4..0608cc0 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -17,7 +17,7 @@ package android.content.res; -import com.android.common.XmlUtils; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java index 2411177..5e90b91 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -24,7 +24,8 @@ import android.util.SparseArray; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; /** * Conveniences for retrieving data out of a compiled string resource. diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 8f0003b..a7fb31d 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -5,7 +5,8 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; import java.util.Arrays; diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index 3c2c30a..ad1bfb2 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -17,7 +17,8 @@ package android.content.res; import android.util.TypedValue; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index f959fee..6941e57 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -16,11 +16,12 @@ package android.net; +import com.android.internal.net.DomainNameValidator; + import android.os.SystemProperties; import android.util.Config; import android.util.Log; -import com.android.common.DomainNameValidator; import java.io.IOException; import java.net.InetAddress; diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java index fa13894..a572f60 100644 --- a/core/java/android/net/WebAddress.java +++ b/core/java/android/net/WebAddress.java @@ -16,7 +16,7 @@ package android.net; -import static com.android.common.Patterns.GOOD_IRI_CHAR; +import static android.util.Patterns.GOOD_IRI_CHAR; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java new file mode 100644 index 0000000..3517737 --- /dev/null +++ b/core/java/android/net/http/AndroidHttpClient.java @@ -0,0 +1,447 @@ +/* + * 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.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 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 android.content.Context; +import android.content.ContentResolver; +import android.net.SSLCertificateSocketFactory; +import android.net.SSLSessionCache; +import android.os.Looper; +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. + * + * <p>This client processes cookies but does not retain them by default. + * To retain cookies, simply add a cookie store to the HttpContext:</p> + * + * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre> + */ +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 context to use for caching SSL sessions (may be null for no caching) + * @return AndroidHttpClient for you to use for all your requests. + */ + public static AndroidHttpClient newInstance(String userAgent, Context context) { + 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); + + // Use a session cache for SSL sockets + SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context); + + // 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", + SSLCertificateSocketFactory.getHttpSocketFactory(30 * 1000, 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); + } + + /** + * 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> T execute(HttpUriRequest request, + ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException { + return delegate.execute(request, responseHandler); + } + + public <T> T execute(HttpUriRequest request, + ResponseHandler<? extends T> responseHandler, HttpContext context) + throws IOException, ClientProtocolException { + return delegate.execute(request, responseHandler, context); + } + + public <T> T execute(HttpHost target, HttpRequest request, + ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return delegate.execute(target, request, responseHandler); + } + + public <T> T execute(HttpHost target, HttpRequest request, + ResponseHandler<? extends T> 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) { + return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant. + } + + /* 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); + } + + /** + * 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) { + // Never print auth token -- we used to check ro.secure=0 to + // enable that, but can't do that in unbundled code. + configuration.println(toCurl((HttpUriRequest) request, false)); + } + } + } + + /** + * 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/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java index e1327dd..c527fe4 100644 --- a/core/java/android/net/http/CertificateChainValidator.java +++ b/core/java/android/net/http/CertificateChainValidator.java @@ -16,7 +16,8 @@ package android.net.http; -import com.android.common.DomainNameValidator; + +import com.android.internal.net.DomainNameValidator; import org.apache.harmony.xnet.provider.jsse.SSLParameters; diff --git a/core/java/android/net/http/HttpDateTime.java b/core/java/android/net/http/HttpDateTime.java new file mode 100644 index 0000000..c7a31ee --- /dev/null +++ b/core/java/android/net/http/HttpDateTime.java @@ -0,0 +1,225 @@ +/* + * 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 android.text.format.Time; + +import java.util.Calendar; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Helper for parsing an HTTP date. + */ +public final class HttpDateTime { + + /* + * Regular expression for parsing HTTP-date. + * + * Wdy, DD Mon YYYY HH:MM:SS GMT + * RFC 822, updated by RFC 1123 + * + * Weekday, DD-Mon-YY HH:MM:SS GMT + * RFC 850, obsoleted by RFC 1036 + * + * Wdy Mon DD HH:MM:SS YYYY + * ANSI C's asctime() format + * + * with following variations + * + * Wdy, DD-Mon-YYYY HH:MM:SS GMT + * Wdy, (SP)D Mon YYYY HH:MM:SS GMT + * Wdy,DD Mon YYYY HH:MM:SS GMT + * Wdy, DD-Mon-YY HH:MM:SS GMT + * Wdy, DD Mon YYYY HH:MM:SS -HHMM + * Wdy, DD Mon YYYY HH:MM:SS + * Wdy Mon (SP)D HH:MM:SS YYYY + * Wdy Mon DD HH:MM:SS YYYY GMT + * + * HH can be H if the first digit is zero. + * + * Mon can be the full name of the month. + */ + private static final String HTTP_DATE_RFC_REGEXP = + "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" + + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; + + private static final String HTTP_DATE_ANSIC_REGEXP = + "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" + + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; + + /** + * The compiled version of the HTTP-date regular expressions. + */ + private static final Pattern HTTP_DATE_RFC_PATTERN = + Pattern.compile(HTTP_DATE_RFC_REGEXP); + private static final Pattern HTTP_DATE_ANSIC_PATTERN = + Pattern.compile(HTTP_DATE_ANSIC_REGEXP); + + private static class TimeOfDay { + TimeOfDay(int h, int m, int s) { + this.hour = h; + this.minute = m; + this.second = s; + } + + int hour; + int minute; + int second; + } + + public static Long parse(String timeString) + throws IllegalArgumentException { + + int date = 1; + int month = Calendar.JANUARY; + int year = 1970; + TimeOfDay timeOfDay; + + Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); + if (rfcMatcher.find()) { + date = getDate(rfcMatcher.group(1)); + month = getMonth(rfcMatcher.group(2)); + year = getYear(rfcMatcher.group(3)); + timeOfDay = getTime(rfcMatcher.group(4)); + } else { + Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); + if (ansicMatcher.find()) { + month = getMonth(ansicMatcher.group(1)); + date = getDate(ansicMatcher.group(2)); + timeOfDay = getTime(ansicMatcher.group(3)); + year = getYear(ansicMatcher.group(4)); + } else { + throw new IllegalArgumentException(); + } + } + + // FIXME: Y2038 BUG! + if (year >= 2038) { + year = 2038; + month = Calendar.JANUARY; + date = 1; + } + + Time time = new Time(Time.TIMEZONE_UTC); + time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, + month, year); + return time.toMillis(false /* use isDst */); + } + + private static int getDate(String dateString) { + if (dateString.length() == 2) { + return (dateString.charAt(0) - '0') * 10 + + (dateString.charAt(1) - '0'); + } else { + return (dateString.charAt(0) - '0'); + } + } + + /* + * jan = 9 + 0 + 13 = 22 + * feb = 5 + 4 + 1 = 10 + * mar = 12 + 0 + 17 = 29 + * apr = 0 + 15 + 17 = 32 + * may = 12 + 0 + 24 = 36 + * jun = 9 + 20 + 13 = 42 + * jul = 9 + 20 + 11 = 40 + * aug = 0 + 20 + 6 = 26 + * sep = 18 + 4 + 15 = 37 + * oct = 14 + 2 + 19 = 35 + * nov = 13 + 14 + 21 = 48 + * dec = 3 + 4 + 2 = 9 + */ + private static int getMonth(String monthString) { + int hash = Character.toLowerCase(monthString.charAt(0)) + + Character.toLowerCase(monthString.charAt(1)) + + Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; + switch (hash) { + case 22: + return Calendar.JANUARY; + case 10: + return Calendar.FEBRUARY; + case 29: + return Calendar.MARCH; + case 32: + return Calendar.APRIL; + case 36: + return Calendar.MAY; + case 42: + return Calendar.JUNE; + case 40: + return Calendar.JULY; + case 26: + return Calendar.AUGUST; + case 37: + return Calendar.SEPTEMBER; + case 35: + return Calendar.OCTOBER; + case 48: + return Calendar.NOVEMBER; + case 9: + return Calendar.DECEMBER; + default: + throw new IllegalArgumentException(); + } + } + + private static int getYear(String yearString) { + if (yearString.length() == 2) { + int year = (yearString.charAt(0) - '0') * 10 + + (yearString.charAt(1) - '0'); + if (year >= 70) { + return year + 1900; + } else { + return year + 2000; + } + } else if (yearString.length() == 3) { + // According to RFC 2822, three digit years should be added to 1900. + int year = (yearString.charAt(0) - '0') * 100 + + (yearString.charAt(1) - '0') * 10 + + (yearString.charAt(2) - '0'); + return year + 1900; + } else if (yearString.length() == 4) { + return (yearString.charAt(0) - '0') * 1000 + + (yearString.charAt(1) - '0') * 100 + + (yearString.charAt(2) - '0') * 10 + + (yearString.charAt(3) - '0'); + } else { + return 1970; + } + } + + private static TimeOfDay getTime(String timeString) { + // HH might be H + int i = 0; + int hour = timeString.charAt(i++) - '0'; + if (timeString.charAt(i) != ':') + hour = hour * 10 + (timeString.charAt(i++) - '0'); + // Skip ':' + i++; + + int minute = (timeString.charAt(i++) - '0') * 10 + + (timeString.charAt(i++) - '0'); + // Skip ':' + i++; + + int second = (timeString.charAt(i++) - '0') * 10 + + (timeString.charAt(i++) - '0'); + + return new TimeOfDay(hour, minute, second); + } +} diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index adeef54..d965962 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -29,8 +29,8 @@ import android.telephony.SmsMessage; import android.text.TextUtils; import android.util.Config; import android.util.Log; +import android.util.Patterns; -import com.android.common.Patterns; import java.util.HashSet; import java.util.Set; diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java index 7c15cec..bf411e1 100644 --- a/core/java/android/speech/RecognizerIntent.java +++ b/core/java/android/speech/RecognizerIntent.java @@ -32,6 +32,15 @@ import android.os.Bundle; * Constants for supporting speech recognition through starting an {@link Intent} */ public class RecognizerIntent { + /** + * The extra key used in an intent to the speech recognizer for voice search. Not + * generally to be used by developers. The system search dialog uses this, for example, + * to set a calling package for identification by a voice search API. If this extra + * is set by anyone but the system process, it should be overridden by the voice search + * implementation. + */ + public final static String EXTRA_CALLING_PACKAGE = "calling_package"; + private RecognizerIntent() { // Not for instantiating. } diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java index 862305b..04730ec 100644 --- a/core/java/android/text/AutoText.java +++ b/core/java/android/text/AutoText.java @@ -18,7 +18,9 @@ package android.text; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; + import android.view.View; import org.xmlpull.v1.XmlPullParser; diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 33ecc01..07e71f9 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -46,7 +46,8 @@ import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Log; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; import java.io.IOException; import java.io.StringReader; diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index 7f87365..9860588 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -22,10 +22,10 @@ import android.text.style.URLSpan; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; +import android.util.Patterns; import android.webkit.WebView; import android.widget.TextView; -import com.android.common.Patterns; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java new file mode 100644 index 0000000..2ee6e8a --- /dev/null +++ b/core/java/android/util/Patterns.java @@ -0,0 +1,232 @@ +/* + * 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.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Commonly used regular expression patterns. + */ +public class Patterns { + /** + * Regular expression to match all IANA top-level domains. + * List accurate as of 2010/02/05. List taken from: + * http://data.iana.org/TLD/tlds-alpha-by-domain.txt + * This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py + */ + public static final String TOP_LEVEL_DOMAIN_STR = + "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])" + + "|(biz|b[abdefghijmnorstvwyz])" + + "|(cat|com|coop|c[acdfghiklmnoruvxyz])" + + "|d[ejkmoz]" + + "|(edu|e[cegrstu])" + + "|f[ijkmor]" + + "|(gov|g[abdefghilmnpqrstuwy])" + + "|h[kmnrtu]" + + "|(info|int|i[delmnoqrst])" + + "|(jobs|j[emop])" + + "|k[eghimnprwyz]" + + "|l[abcikrstuvy]" + + "|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])" + + "|(name|net|n[acefgilopruz])" + + "|(org|om)" + + "|(pro|p[aefghklmnrstwy])" + + "|qa" + + "|r[eosuw]" + + "|s[abcdeghijklmnortuvyz]" + + "|(tel|travel|t[cdfghjklmnoprtvwz])" + + "|u[agksyz]" + + "|v[aceginu]" + + "|w[fs]" + + "|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)" + + "|y[etu]" + + "|z[amw])"; + + /** + * Regular expression pattern to match all IANA top-level domains. + */ + public static final Pattern TOP_LEVEL_DOMAIN = + Pattern.compile(TOP_LEVEL_DOMAIN_STR); + + /** + * Regular expression to match all IANA top-level domains for WEB_URL. + * List accurate as of 2010/02/05. List taken from: + * http://data.iana.org/TLD/tlds-alpha-by-domain.txt + * This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py + */ + public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL = + "(?:" + + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])" + + "|(?:biz|b[abdefghijmnorstvwyz])" + + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])" + + "|d[ejkmoz]" + + "|(?:edu|e[cegrstu])" + + "|f[ijkmor]" + + "|(?:gov|g[abdefghilmnpqrstuwy])" + + "|h[kmnrtu]" + + "|(?:info|int|i[delmnoqrst])" + + "|(?:jobs|j[emop])" + + "|k[eghimnprwyz]" + + "|l[abcikrstuvy]" + + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])" + + "|(?:name|net|n[acefgilopruz])" + + "|(?:org|om)" + + "|(?:pro|p[aefghklmnrstwy])" + + "|qa" + + "|r[eosuw]" + + "|s[abcdeghijklmnortuvyz]" + + "|(?:tel|travel|t[cdfghjklmnoprtvwz])" + + "|u[agksyz]" + + "|v[aceginu]" + + "|w[fs]" + + "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)" + + "|y[etu]" + + "|z[amw]))"; + + /** + * Good characters for Internationalized Resource Identifiers (IRI). + * This comprises most common used Unicode characters allowed in IRI + * as detailed in RFC 3987. + * Specifically, those two byte Unicode characters are not included. + */ + public static final String GOOD_IRI_CHAR = + "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; + + /** + * Regular expression pattern to match most part of RFC 3987 + * Internationalized URLs, aka IRIs. Commonly used Unicode characters are + * added. + */ + public static final Pattern WEB_URL = Pattern.compile( + "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" + + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" + + "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host + + TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL + + "|(?:(?:25[0-5]|2[0-4]" // or ip address + + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]" + + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]" + + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + + "|[1-9][0-9]|[0-9])))" + + "(?:\\:\\d{1,5})?)" // plus option port number + + "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?" + + "(?:\\b|$)"); // and finally, a word boundary or end of + // input. This is to stop foo.sure from + // matching as foo.su + + public static final Pattern IP_ADDRESS + = Pattern.compile( + "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + + "|[1-9][0-9]|[0-9]))"); + + public static final Pattern DOMAIN_NAME + = Pattern.compile( + "(((([" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]*)*[" + GOOD_IRI_CHAR + "]\\.)+" + + TOP_LEVEL_DOMAIN + ")|" + + IP_ADDRESS + ")"); + + public static final Pattern EMAIL_ADDRESS + = Pattern.compile( + "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + + "\\@" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + + "(" + + "\\." + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + + ")+" + ); + + /** + * This pattern is intended for searching for things that look like they + * might be phone numbers in arbitrary text, not for validating whether + * something is in fact a phone number. It will miss many things that + * are legitimate phone numbers. + * + * <p> The pattern matches the following: + * <ul> + * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes + * may follow. + * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes. + * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes. + * </ul> + */ + public static final Pattern PHONE + = Pattern.compile( // sdd = space, dot, or dash + "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>* + + "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>* + + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit> + + /** + * Convenience method to take all of the non-null matching groups in a + * regex Matcher and return them as a concatenated string. + * + * @param matcher The Matcher object from which grouped text will + * be extracted + * + * @return A String comprising all of the non-null matched + * groups concatenated together + */ + public static final String concatGroups(Matcher matcher) { + StringBuilder b = new StringBuilder(); + final int numGroups = matcher.groupCount(); + + for (int i = 1; i <= numGroups; i++) { + String s = matcher.group(i); + + System.err.println("Group(" + i + ") : " + s); + + if (s != null) { + b.append(s); + } + } + + return b.toString(); + } + + /** + * Convenience method to return only the digits and plus signs + * in the matching string. + * + * @param matcher The Matcher object from which digits and plus will + * be extracted + * + * @return A String comprising all of the digits and plus in + * the match + */ + public static final String digitsAndPlusOnly(Matcher matcher) { + StringBuilder buffer = new StringBuilder(); + String matchingRegion = matcher.group(); + + for (int i = 0, size = matchingRegion.length(); i < size; i++) { + char character = matchingRegion.charAt(i); + + if (character == '+' || Character.isDigit(character)) { + buffer.append(character); + } + } + return buffer.toString(); + } + + /** + * Do not create this static utility class. + */ + private Patterns() {} +} diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 4f496d7..0fc70d5 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -27,7 +27,7 @@ import java.io.IOException; import java.util.TimeZone; import java.util.Date; -import com.android.common.XmlUtils; +import com.android.internal.util.XmlUtils; /** * A class containing utility methods related to time zones. diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java index 8f855cd..ecedbe1 100644 --- a/core/java/android/util/XmlPullAttributes.java +++ b/core/java/android/util/XmlPullAttributes.java @@ -19,7 +19,8 @@ package android.util; import org.xmlpull.v1.XmlPullParser; import android.util.AttributeSet; -import com.android.common.XmlUtils; + +import com.android.internal.util.XmlUtils; /** * Provides an implementation of AttributeSet on top of an XmlPullParser. diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index 87cab3c..647556b 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -18,6 +18,7 @@ package android.webkit; import android.content.Context; import android.net.http.Headers; +import android.net.http.HttpDateTime; import android.os.FileUtils; import android.util.Log; import java.io.File; @@ -30,7 +31,6 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Map; -import com.android.common.HttpDateTime; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.SHA1Digest; diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 84e34bc..1d28731 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -18,9 +18,9 @@ package android.webkit; import android.net.ParseException; import android.net.WebAddress; +import android.net.http.HttpDateTime; import android.util.Log; -import com.android.common.HttpDateTime; import java.util.ArrayList; import java.util.Arrays; diff --git a/core/java/com/android/internal/net/DNParser.java b/core/java/com/android/internal/net/DNParser.java new file mode 100644 index 0000000..5254207 --- /dev/null +++ b/core/java/com/android/internal/net/DNParser.java @@ -0,0 +1,450 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.net; + + +import android.util.Log; + +import java.io.IOException; + +import javax.security.auth.x500.X500Principal; + +/** + * A simple distinguished name(DN) parser. + * + * <p>This class is based on org.apache.harmony.security.x509.DNParser. It's customized to remove + * external references which are unnecessary for our requirements. + * + * <p>This class is only meant for extracting a string value from a DN. e.g. it doesn't support + * values in the hex-string style. + * + * <p>This class is used by {@link DomainNameValidator} only. However, in order to make this + * class visible from unit tests, it's made public. + * + * @hide + */ +public final class DNParser { + private static final String TAG = "DNParser"; + + /** DN to be parsed. */ + private final String dn; + + // length of distinguished name string + private final int length; + + private int pos, beg, end; + + // tmp vars to store positions of the currently parsed item + private int cur; + + // distinguished name chars + private char[] chars; + + /** + * Exception message thrown when we failed to parse DN, which shouldn't happen because we + * only handle DNs that {@link X500Principal#getName} returns, which shouldn't be malformed. + */ + private static final String ERROR_PARSE_ERROR = "Failed to parse DN"; + + /** + * Constructor. + * + * @param principal - {@link X500Principal} to be parsed + */ + public DNParser(X500Principal principal) { + this.dn = principal.getName(X500Principal.RFC2253); + this.length = dn.length(); + } + + // gets next attribute type: (ALPHA 1*keychar) / oid + private String nextAT() throws IOException { + + // skip preceding space chars, they can present after + // comma or semicolon (compatibility with RFC 1779) + for (; pos < length && chars[pos] == ' '; pos++) { + } + if (pos == length) { + return null; // reached the end of DN + } + + // mark the beginning of attribute type + beg = pos; + + // attribute type chars + pos++; + for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) { + // we don't follow exact BNF syntax here: + // accept any char except space and '=' + } + if (pos >= length) { + // unexpected end of DN + throw new IOException(ERROR_PARSE_ERROR); + } + + // mark the end of attribute type + end = pos; + + // skip trailing space chars between attribute type and '=' + // (compatibility with RFC 1779) + if (chars[pos] == ' ') { + for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) { + } + + if (chars[pos] != '=' || pos == length) { + // unexpected end of DN + throw new IOException(ERROR_PARSE_ERROR); + } + } + + pos++; //skip '=' char + + // skip space chars between '=' and attribute value + // (compatibility with RFC 1779) + for (; pos < length && chars[pos] == ' '; pos++) { + } + + // in case of oid attribute type skip its prefix: "oid." or "OID." + // (compatibility with RFC 1779) + if ((end - beg > 4) && (chars[beg + 3] == '.') + && (chars[beg] == 'O' || chars[beg] == 'o') + && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i') + && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) { + beg += 4; + } + + return new String(chars, beg, end - beg); + } + + // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION + private String quotedAV() throws IOException { + + pos++; + beg = pos; + end = beg; + while (true) { + + if (pos == length) { + // unexpected end of DN + throw new IOException(ERROR_PARSE_ERROR); + } + + if (chars[pos] == '"') { + // enclosing quotation was found + pos++; + break; + } else if (chars[pos] == '\\') { + chars[end] = getEscaped(); + } else { + // shift char: required for string with escaped chars + chars[end] = chars[pos]; + } + pos++; + end++; + } + + // skip trailing space chars before comma or semicolon. + // (compatibility with RFC 1779) + for (; pos < length && chars[pos] == ' '; pos++) { + } + + return new String(chars, beg, end - beg); + } + + // gets hex string attribute value: "#" hexstring + private String hexAV() throws IOException { + + if (pos + 4 >= length) { + // encoded byte array must be not less then 4 c + throw new IOException(ERROR_PARSE_ERROR); + } + + beg = pos; // store '#' position + pos++; + while (true) { + + // check for end of attribute value + // looks for space and component separators + if (pos == length || chars[pos] == '+' || chars[pos] == ',' + || chars[pos] == ';') { + end = pos; + break; + } + + if (chars[pos] == ' ') { + end = pos; + pos++; + // skip trailing space chars before comma or semicolon. + // (compatibility with RFC 1779) + for (; pos < length && chars[pos] == ' '; pos++) { + } + break; + } else if (chars[pos] >= 'A' && chars[pos] <= 'F') { + chars[pos] += 32; //to low case + } + + pos++; + } + + // verify length of hex string + // encoded byte array must be not less then 4 and must be even number + int hexLen = end - beg; // skip first '#' char + if (hexLen < 5 || (hexLen & 1) == 0) { + throw new IOException(ERROR_PARSE_ERROR); + } + + // get byte encoding from string representation + byte[] encoded = new byte[hexLen / 2]; + for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) { + encoded[i] = (byte) getByte(p); + } + + return new String(chars, beg, hexLen); + } + + // gets string attribute value: *( stringchar / pair ) + private String escapedAV() throws IOException { + + beg = pos; + end = pos; + while (true) { + + if (pos >= length) { + // the end of DN has been found + return new String(chars, beg, end - beg); + } + + switch (chars[pos]) { + case '+': + case ',': + case ';': + // separator char has beed found + return new String(chars, beg, end - beg); + case '\\': + // escaped char + chars[end++] = getEscaped(); + pos++; + break; + case ' ': + // need to figure out whether space defines + // the end of attribute value or not + cur = end; + + pos++; + chars[end++] = ' '; + + for (; pos < length && chars[pos] == ' '; pos++) { + chars[end++] = ' '; + } + if (pos == length || chars[pos] == ',' || chars[pos] == '+' + || chars[pos] == ';') { + // separator char or the end of DN has beed found + return new String(chars, beg, cur - beg); + } + break; + default: + chars[end++] = chars[pos]; + pos++; + } + } + } + + // returns escaped char + private char getEscaped() throws IOException { + + pos++; + if (pos == length) { + throw new IOException(ERROR_PARSE_ERROR); + } + + switch (chars[pos]) { + case '"': + case '\\': + case ',': + case '=': + case '+': + case '<': + case '>': + case '#': + case ';': + case ' ': + case '*': + case '%': + case '_': + //FIXME: escaping is allowed only for leading or trailing space char + return chars[pos]; + default: + // RFC doesn't explicitly say that escaped hex pair is + // interpreted as UTF-8 char. It only contains an example of such DN. + return getUTF8(); + } + } + + // decodes UTF-8 char + // see http://www.unicode.org for UTF-8 bit distribution table + private char getUTF8() throws IOException { + + int res = getByte(pos); + pos++; //FIXME tmp + + if (res < 128) { // one byte: 0-7F + return (char) res; + } else if (res >= 192 && res <= 247) { + + int count; + if (res <= 223) { // two bytes: C0-DF + count = 1; + res = res & 0x1F; + } else if (res <= 239) { // three bytes: E0-EF + count = 2; + res = res & 0x0F; + } else { // four bytes: F0-F7 + count = 3; + res = res & 0x07; + } + + int b; + for (int i = 0; i < count; i++) { + pos++; + if (pos == length || chars[pos] != '\\') { + return 0x3F; //FIXME failed to decode UTF-8 char - return '?' + } + pos++; + + b = getByte(pos); + pos++; //FIXME tmp + if ((b & 0xC0) != 0x80) { + return 0x3F; //FIXME failed to decode UTF-8 char - return '?' + } + + res = (res << 6) + (b & 0x3F); + } + return (char) res; + } else { + return 0x3F; //FIXME failed to decode UTF-8 char - return '?' + } + } + + // Returns byte representation of a char pair + // The char pair is composed of DN char in + // specified 'position' and the next char + // According to BNF syntax: + // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + // / "a" / "b" / "c" / "d" / "e" / "f" + private int getByte(int position) throws IOException { + + if ((position + 1) >= length) { + // to avoid ArrayIndexOutOfBoundsException + throw new IOException(ERROR_PARSE_ERROR); + } + + int b1, b2; + + b1 = chars[position]; + if (b1 >= '0' && b1 <= '9') { + b1 = b1 - '0'; + } else if (b1 >= 'a' && b1 <= 'f') { + b1 = b1 - 87; // 87 = 'a' - 10 + } else if (b1 >= 'A' && b1 <= 'F') { + b1 = b1 - 55; // 55 = 'A' - 10 + } else { + throw new IOException(ERROR_PARSE_ERROR); + } + + b2 = chars[position + 1]; + if (b2 >= '0' && b2 <= '9') { + b2 = b2 - '0'; + } else if (b2 >= 'a' && b2 <= 'f') { + b2 = b2 - 87; // 87 = 'a' - 10 + } else if (b2 >= 'A' && b2 <= 'F') { + b2 = b2 - 55; // 55 = 'A' - 10 + } else { + throw new IOException(ERROR_PARSE_ERROR); + } + + return (b1 << 4) + b2; + } + + /** + * Parses the DN and returns the attribute value for an attribute type. + * + * @param attributeType attribute type to look for (e.g. "ca") + * @return value of the attribute that first found, or null if none found + */ + public String find(String attributeType) { + try { + // Initialize internal state. + pos = 0; + beg = 0; + end = 0; + cur = 0; + chars = dn.toCharArray(); + + String attType = nextAT(); + if (attType == null) { + return null; + } + while (true) { + String attValue = ""; + + if (pos == length) { + return null; + } + + switch (chars[pos]) { + case '"': + attValue = quotedAV(); + break; + case '#': + attValue = hexAV(); + break; + case '+': + case ',': + case ';': // compatibility with RFC 1779: semicolon can separate RDNs + //empty attribute value + break; + default: + attValue = escapedAV(); + } + + if (attributeType.equalsIgnoreCase(attType)) { + return attValue; + } + + if (pos >= length) { + return null; + } + + if (chars[pos] == ',' || chars[pos] == ';') { + } else if (chars[pos] != '+') { + throw new IOException(ERROR_PARSE_ERROR); + } + + pos++; + attType = nextAT(); + if (attType == null) { + throw new IOException(ERROR_PARSE_ERROR); + } + } + } catch (IOException e) { + // Parse error shouldn't happen, because we only handle DNs that + // X500Principal.getName() returns, which shouldn't be malformed. + Log.e(TAG, "Failed to parse DN: " + dn); + return null; + } + } +} diff --git a/core/java/com/android/internal/net/DomainNameValidator.java b/core/java/com/android/internal/net/DomainNameValidator.java new file mode 100644 index 0000000..dbd5019 --- /dev/null +++ b/core/java/com/android/internal/net/DomainNameValidator.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2010 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.net; + + +import android.util.Config; +import android.util.Log; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.security.auth.x500.X500Principal; + +/** @hide */ +public class DomainNameValidator { + private final static String TAG = "DomainNameValidator"; + + private static final boolean DEBUG = false; + private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV; + + private static Pattern QUICK_IP_PATTERN; + static { + try { + QUICK_IP_PATTERN = Pattern.compile("^[a-f0-9\\.:]+$"); + } catch (PatternSyntaxException e) {} + } + + private static final int ALT_DNS_NAME = 2; + private static final int ALT_IPA_NAME = 7; + + /** + * Checks the site certificate against the domain name of the site being visited + * @param certificate The certificate to check + * @param thisDomain The domain name of the site being visited + * @return True iff if there is a domain match as specified by RFC2818 + */ + public static boolean match(X509Certificate certificate, String thisDomain) { + if (certificate == null || thisDomain == null || thisDomain.length() == 0) { + return false; + } + + thisDomain = thisDomain.toLowerCase(); + if (!isIpAddress(thisDomain)) { + return matchDns(certificate, thisDomain); + } else { + return matchIpAddress(certificate, thisDomain); + } + } + + /** + * @return True iff the domain name is specified as an IP address + */ + private static boolean isIpAddress(String domain) { + boolean rval = (domain != null && domain.length() != 0); + if (rval) { + try { + // do a quick-dirty IP match first to avoid DNS lookup + rval = QUICK_IP_PATTERN.matcher(domain).matches(); + if (rval) { + rval = domain.equals( + InetAddress.getByName(domain).getHostAddress()); + } + } catch (UnknownHostException e) { + String errorMessage = e.getMessage(); + if (errorMessage == null) { + errorMessage = "unknown host exception"; + } + + if (LOG_ENABLED) { + Log.v(TAG, "DomainNameValidator.isIpAddress(): " + errorMessage); + } + + rval = false; + } + } + + return rval; + } + + /** + * Checks the site certificate against the IP domain name of the site being visited + * @param certificate The certificate to check + * @param thisDomain The DNS domain name of the site being visited + * @return True iff if there is a domain match as specified by RFC2818 + */ + private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) { + if (LOG_ENABLED) { + Log.v(TAG, "DomainNameValidator.matchIpAddress(): this domain: " + thisDomain); + } + + try { + Collection subjectAltNames = certificate.getSubjectAlternativeNames(); + if (subjectAltNames != null) { + Iterator i = subjectAltNames.iterator(); + while (i.hasNext()) { + List altNameEntry = (List)(i.next()); + if (altNameEntry != null && 2 <= altNameEntry.size()) { + Integer altNameType = (Integer)(altNameEntry.get(0)); + if (altNameType != null) { + if (altNameType.intValue() == ALT_IPA_NAME) { + String altName = (String)(altNameEntry.get(1)); + if (altName != null) { + if (LOG_ENABLED) { + Log.v(TAG, "alternative IP: " + altName); + } + if (thisDomain.equalsIgnoreCase(altName)) { + return true; + } + } + } + } + } + } + } + } catch (CertificateParsingException e) {} + + return false; + } + + /** + * Checks the site certificate against the DNS domain name of the site being visited + * @param certificate The certificate to check + * @param thisDomain The DNS domain name of the site being visited + * @return True iff if there is a domain match as specified by RFC2818 + */ + private static boolean matchDns(X509Certificate certificate, String thisDomain) { + boolean hasDns = false; + try { + Collection subjectAltNames = certificate.getSubjectAlternativeNames(); + if (subjectAltNames != null) { + Iterator i = subjectAltNames.iterator(); + while (i.hasNext()) { + List altNameEntry = (List)(i.next()); + if (altNameEntry != null && 2 <= altNameEntry.size()) { + Integer altNameType = (Integer)(altNameEntry.get(0)); + if (altNameType != null) { + if (altNameType.intValue() == ALT_DNS_NAME) { + hasDns = true; + String altName = (String)(altNameEntry.get(1)); + if (altName != null) { + if (matchDns(thisDomain, altName)) { + return true; + } + } + } + } + } + } + } + } catch (CertificateParsingException e) { + String errorMessage = e.getMessage(); + if (errorMessage == null) { + errorMessage = "failed to parse certificate"; + } + + Log.w(TAG, "DomainNameValidator.matchDns(): " + errorMessage); + return false; + } + + if (!hasDns) { + final String cn = new DNParser(certificate.getSubjectX500Principal()) + .find("cn"); + if (LOG_ENABLED) { + Log.v(TAG, "Validating subject: DN:" + + certificate.getSubjectX500Principal().getName(X500Principal.CANONICAL) + + " CN:" + cn); + } + if (cn != null) { + return matchDns(thisDomain, cn); + } + } + + return false; + } + + /** + * @param thisDomain The domain name of the site being visited + * @param thatDomain The domain name from the certificate + * @return True iff thisDomain matches thatDomain as specified by RFC2818 + */ + // not private for testing + public static boolean matchDns(String thisDomain, String thatDomain) { + if (LOG_ENABLED) { + Log.v(TAG, "DomainNameValidator.matchDns():" + + " this domain: " + thisDomain + + " that domain: " + thatDomain); + } + + if (thisDomain == null || thisDomain.length() == 0 || + thatDomain == null || thatDomain.length() == 0) { + return false; + } + + thatDomain = thatDomain.toLowerCase(); + + // (a) domain name strings are equal, ignoring case: X matches X + boolean rval = thisDomain.equals(thatDomain); + if (!rval) { + String[] thisDomainTokens = thisDomain.split("\\."); + String[] thatDomainTokens = thatDomain.split("\\."); + + int thisDomainTokensNum = thisDomainTokens.length; + int thatDomainTokensNum = thatDomainTokens.length; + + // (b) OR thatHost is a '.'-suffix of thisHost: Z.Y.X matches X + if (thisDomainTokensNum >= thatDomainTokensNum) { + for (int i = thatDomainTokensNum - 1; i >= 0; --i) { + rval = thisDomainTokens[i].equals(thatDomainTokens[i]); + if (!rval) { + // (c) OR we have a special *-match: + // *.Y.X matches Z.Y.X but *.X doesn't match Z.Y.X + rval = (i == 0 && thisDomainTokensNum == thatDomainTokensNum); + if (rval) { + rval = thatDomainTokens[0].equals("*"); + if (!rval) { + // (d) OR we have a *-component match: + // f*.com matches foo.com but not bar.com + rval = domainTokenMatch( + thisDomainTokens[0], thatDomainTokens[0]); + } + } + break; + } + } + } else { + // (e) OR thatHost has a '*.'-prefix of thisHost: + // *.Y.X matches Y.X + rval = thatDomain.equals("*." + thisDomain); + } + } + + return rval; + } + + /** + * @param thisDomainToken The domain token from the current domain name + * @param thatDomainToken The domain token from the certificate + * @return True iff thisDomainToken matches thatDomainToken, using the + * wildcard match as specified by RFC2818-3.1. For example, f*.com must + * match foo.com but not bar.com + */ + private static boolean domainTokenMatch(String thisDomainToken, String thatDomainToken) { + if (thisDomainToken != null && thatDomainToken != null) { + int starIndex = thatDomainToken.indexOf('*'); + if (starIndex >= 0) { + if (thatDomainToken.length() - 1 <= thisDomainToken.length()) { + String prefix = thatDomainToken.substring(0, starIndex); + String suffix = thatDomainToken.substring(starIndex + 1); + + return thisDomainToken.startsWith(prefix) && thisDomainToken.endsWith(suffix); + } + } + } + + return false; + } +} diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 9e5bdff..2369d25 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -20,7 +20,7 @@ package com.android.internal.os; import android.content.Context; import android.content.res.XmlResourceParser; -import com.android.common.XmlUtils; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java new file mode 100644 index 0000000..592a8fa --- /dev/null +++ b/core/java/com/android/internal/util/FastXmlSerializer.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2006 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.util; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; + +/** + * This is a quick and dirty implementation of XmlSerializer that isn't horribly + * painfully slow like the normal one. It only does what is needed for the + * specific XML files being written with it. + */ +public class FastXmlSerializer implements XmlSerializer { + private static final String ESCAPE_TABLE[] = new String[] { + null, null, null, null, null, null, null, null, // 0-7 + null, null, null, null, null, null, null, null, // 8-15 + null, null, null, null, null, null, null, null, // 16-23 + null, null, null, null, null, null, null, null, // 24-31 + null, null, """, null, null, null, "&", null, // 32-39 + null, null, null, null, null, null, null, null, // 40-47 + null, null, null, null, null, null, null, null, // 48-55 + null, null, null, null, "<", null, ">", null, // 56-63 + }; + + private static final int BUFFER_LEN = 8192; + + private final char[] mText = new char[BUFFER_LEN]; + private int mPos; + + private Writer mWriter; + + private OutputStream mOutputStream; + private CharsetEncoder mCharset; + private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN); + + private boolean mInTag; + + private void append(char c) throws IOException { + int pos = mPos; + if (pos >= (BUFFER_LEN-1)) { + flush(); + pos = mPos; + } + mText[pos] = c; + mPos = pos+1; + } + + private void append(String str, int i, final int length) throws IOException { + if (length > BUFFER_LEN) { + final int end = i + length; + while (i < end) { + int next = i + BUFFER_LEN; + append(str, i, next<end ? BUFFER_LEN : (end-i)); + i = next; + } + return; + } + int pos = mPos; + if ((pos+length) > BUFFER_LEN) { + flush(); + pos = mPos; + } + str.getChars(i, i+length, mText, pos); + mPos = pos + length; + } + + private void append(char[] buf, int i, final int length) throws IOException { + if (length > BUFFER_LEN) { + final int end = i + length; + while (i < end) { + int next = i + BUFFER_LEN; + append(buf, i, next<end ? BUFFER_LEN : (end-i)); + i = next; + } + return; + } + int pos = mPos; + if ((pos+length) > BUFFER_LEN) { + flush(); + pos = mPos; + } + System.arraycopy(buf, i, mText, pos, length); + mPos = pos + length; + } + + private void append(String str) throws IOException { + append(str, 0, str.length()); + } + + private void escapeAndAppendString(final String string) throws IOException { + final int N = string.length(); + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int lastPos = 0; + int pos; + for (pos=0; pos<N; pos++) { + char c = string.charAt(pos); + if (c >= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(string, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(string, lastPos, pos-lastPos); + } + + private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int end = start+len; + int lastPos = start; + int pos; + for (pos=start; pos<end; pos++) { + char c = buf[pos]; + if (c >= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + } + + public XmlSerializer attribute(String namespace, String name, String value) throws IOException, + IllegalArgumentException, IllegalStateException { + append(' '); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + append("=\""); + + escapeAndAppendString(value); + append('"'); + return this; + } + + public void cdsect(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void comment(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void docdecl(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { + flush(); + } + + public XmlSerializer endTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(" />\n"); + } else { + append("</"); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + append(">\n"); + } + mInTag = false; + return this; + } + + public void entityRef(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + private void flushBytes() throws IOException { + int position; + if ((position = mBytes.position()) > 0) { + mBytes.flip(); + mOutputStream.write(mBytes.array(), 0, position); + mBytes.clear(); + } + } + + public void flush() throws IOException { + //Log.i("PackageManager", "flush mPos=" + mPos); + if (mPos > 0) { + if (mOutputStream != null) { + CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); + CoderResult result = mCharset.encode(charBuffer, mBytes, true); + while (true) { + if (result.isError()) { + throw new IOException(result.toString()); + } else if (result.isOverflow()) { + flushBytes(); + result = mCharset.encode(charBuffer, mBytes, true); + continue; + } + break; + } + flushBytes(); + mOutputStream.flush(); + } else { + mWriter.write(mText, 0, mPos); + mWriter.flush(); + } + mPos = 0; + } + } + + public int getDepth() { + throw new UnsupportedOperationException(); + } + + public boolean getFeature(String name) { + throw new UnsupportedOperationException(); + } + + public String getName() { + throw new UnsupportedOperationException(); + } + + public String getNamespace() { + throw new UnsupportedOperationException(); + } + + public String getPrefix(String namespace, boolean generatePrefix) + throws IllegalArgumentException { + throw new UnsupportedOperationException(); + } + + public Object getProperty(String name) { + throw new UnsupportedOperationException(); + } + + public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void processingInstruction(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void setFeature(String name, boolean state) throws IllegalArgumentException, + IllegalStateException { + if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { + return; + } + throw new UnsupportedOperationException(); + } + + public void setOutput(OutputStream os, String encoding) throws IOException, + IllegalArgumentException, IllegalStateException { + if (os == null) + throw new IllegalArgumentException(); + if (true) { + try { + mCharset = Charset.forName(encoding).newEncoder(); + } catch (IllegalCharsetNameException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } catch (UnsupportedCharsetException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } + mOutputStream = os; + } else { + setOutput( + encoding == null + ? new OutputStreamWriter(os) + : new OutputStreamWriter(os, encoding)); + } + } + + public void setOutput(Writer writer) throws IOException, IllegalArgumentException, + IllegalStateException { + mWriter = writer; + } + + public void setPrefix(String prefix, String namespace) throws IOException, + IllegalArgumentException, IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void setProperty(String name, Object value) throws IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void startDocument(String encoding, Boolean standalone) throws IOException, + IllegalArgumentException, IllegalStateException { + append("<?xml version='1.0' encoding='utf-8' standalone='" + + (standalone ? "yes" : "no") + "' ?>\n"); + } + + public XmlSerializer startTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">\n"); + } + append('<'); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + mInTag = true; + return this; + } + + public XmlSerializer text(char[] buf, int start, int len) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(buf, start, len); + return this; + } + + public XmlSerializer text(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(text); + return this; + } + +} diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java new file mode 100644 index 0000000..8d8df16 --- /dev/null +++ b/core/java/com/android/internal/util/XmlUtils.java @@ -0,0 +1,797 @@ +/* + * Copyright (C) 2006 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.util; + + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import android.util.Xml; + +/** {@hide} */ +public class XmlUtils +{ + + public static void skipCurrentTag(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + } + } + + public static final int + convertValueToList(CharSequence value, String[] options, int defaultValue) + { + if (null != value) { + for (int i = 0; i < options.length; i++) { + if (value.equals(options[i])) + return i; + } + } + + return defaultValue; + } + + public static final boolean + convertValueToBoolean(CharSequence value, boolean defaultValue) + { + boolean result = false; + + if (null == value) + return defaultValue; + + if (value.equals("1") + || value.equals("true") + || value.equals("TRUE")) + result = true; + + return result; + } + + public static final int + convertValueToInt(CharSequence charSeq, int defaultValue) + { + if (null == charSeq) + return defaultValue; + + String nm = charSeq.toString(); + + // XXX This code is copied from Integer.decode() so we don't + // have to instantiate an Integer! + + int value; + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + + char c = nm.charAt(index + 1); + + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } + else if ('#' == nm.charAt(index)) + { + index++; + base = 16; + } + + return Integer.parseInt(nm.substring(index), base) * sign; + } + + public static final int + convertValueToUnsignedInt(String value, int defaultValue) + { + if (null == value) + return defaultValue; + + return parseUnsignedIntAttribute(value); + } + + public static final int + parseUnsignedIntAttribute(CharSequence charSeq) + { + String value = charSeq.toString(); + + long bits; + int index = 0; + int len = value.length(); + int base = 10; + + if ('0' == value.charAt(index)) { + // Quick check for zero by itself + if (index == (len - 1)) + return 0; + + char c = value.charAt(index + 1); + + if ('x' == c || 'X' == c) { // check for hex + index += 2; + base = 16; + } else { // check for octal + index++; + base = 8; + } + } else if ('#' == value.charAt(index)) { + index++; + base = 16; + } + + return (int) Long.parseLong(value.substring(index), base); + } + + /** + * Flatten a Map into an output stream as XML. The map can later be + * read back with readMapXml(). + * + * @param val The map to be flattened. + * @param out Where to write the XML data. + * + * @see #writeMapXml(Map, String, XmlSerializer) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, OutputStream out) + throws XmlPullParserException, java.io.IOException { + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeMapXml(val, null, serializer); + serializer.endDocument(); + } + + /** + * Flatten a List into an output stream as XML. The list can later be + * read back with readListXml(). + * + * @param val The list to be flattened. + * @param out Where to write the XML data. + * + * @see #writeListXml(List, String, XmlSerializer) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, OutputStream out) + throws XmlPullParserException, java.io.IOException + { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeListXml(val, null, serializer); + serializer.endDocument(); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException + { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + Set s = val.entrySet(); + Iterator i = s.iterator(); + + out.startTag(null, "map"); + if (name != null) { + out.attribute(null, "name", name); + } + + while (i.hasNext()) { + Map.Entry e = (Map.Entry)i.next(); + writeValueXml(e.getValue(), (String)e.getKey(), out); + } + + out.endTag(null, "map"); + } + + /** + * Flatten a List into an XmlSerializer. The list can later be read back + * with readThisListXml(). + * + * @param val The list to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the list into. + * + * @see #writeListXml(List, OutputStream) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException + { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "list"); + if (name != null) { + out.attribute(null, "name", name); + } + + int N = val.size(); + int i=0; + while (i < N) { + writeValueXml(val.get(i), null, out); + i++; + } + + out.endTag(null, "list"); + } + + /** + * Flatten a byte[] into an XmlSerializer. The list can later be read back + * with readThisByteArrayXml(). + * + * @param val The byte array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + */ + public static final void writeByteArrayXml(byte[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "byte-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + StringBuilder sb = new StringBuilder(val.length*2); + for (int i=0; i<N; i++) { + int b = val[i]; + int h = b>>4; + sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); + h = b&0xff; + sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); + } + + out.text(sb.toString()); + + out.endTag(null, "byte-array"); + } + + /** + * Flatten an int[] into an XmlSerializer. The list can later be read back + * with readThisIntArrayXml(). + * + * @param val The int array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeIntArrayXml(int[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "int-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", Integer.toString(val[i])); + out.endTag(null, "item"); + } + + out.endTag(null, "int-array"); + } + + /** + * Flatten an object's value into an XmlSerializer. The value can later + * be read back with readThisValueXml(). + * + * Currently supported value types are: null, String, Integer, Long, + * Float, Double Boolean, Map, List. + * + * @param v The object to be flattened. + * @param name Name attribute to include with this value's tag, or null + * for none. + * @param out XmlSerializer to write the object into. + * + * @see #writeMapXml + * @see #writeListXml + * @see #readValueXml + */ + public static final void writeValueXml(Object v, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException + { + String typeStr; + if (v == null) { + out.startTag(null, "null"); + if (name != null) { + out.attribute(null, "name", name); + } + out.endTag(null, "null"); + return; + } else if (v instanceof String) { + out.startTag(null, "string"); + if (name != null) { + out.attribute(null, "name", name); + } + out.text(v.toString()); + out.endTag(null, "string"); + return; + } else if (v instanceof Integer) { + typeStr = "int"; + } else if (v instanceof Long) { + typeStr = "long"; + } else if (v instanceof Float) { + typeStr = "float"; + } else if (v instanceof Double) { + typeStr = "double"; + } else if (v instanceof Boolean) { + typeStr = "boolean"; + } else if (v instanceof byte[]) { + writeByteArrayXml((byte[])v, name, out); + return; + } else if (v instanceof int[]) { + writeIntArrayXml((int[])v, name, out); + return; + } else if (v instanceof Map) { + writeMapXml((Map)v, name, out); + return; + } else if (v instanceof List) { + writeListXml((List)v, name, out); + return; + } else if (v instanceof CharSequence) { + // XXX This is to allow us to at least write something if + // we encounter styled text... but it means we will drop all + // of the styling information. :( + out.startTag(null, "string"); + if (name != null) { + out.attribute(null, "name", name); + } + out.text(v.toString()); + out.endTag(null, "string"); + return; + } else { + throw new RuntimeException("writeValueXml: unable to write value " + v); + } + + out.startTag(null, typeStr); + if (name != null) { + out.attribute(null, "name", name); + } + out.attribute(null, "value", v.toString()); + out.endTag(null, typeStr); + } + + /** + * Read a HashMap from an InputStream containing XML. The stream can + * previously have been written by writeMapXml(). + * + * @param in The InputStream from which to read. + * + * @return HashMap The resulting map. + * + * @see #readListXml + * @see #readValueXml + * @see #readThisMapXml + * #see #writeMapXml + */ + public static final HashMap readMapXml(InputStream in) + throws XmlPullParserException, java.io.IOException + { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (HashMap)readValueXml(parser, new String[1]); + } + + /** + * Read an ArrayList from an InputStream containing XML. The stream can + * previously have been written by writeListXml(). + * + * @param in The InputStream from which to read. + * + * @return HashMap The resulting list. + * + * @see #readMapXml + * @see #readValueXml + * @see #readThisListXml + * @see #writeListXml + */ + public static final ArrayList readListXml(InputStream in) + throws XmlPullParserException, java.io.IOException + { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (ArrayList)readValueXml(parser, new String[1]); + } + + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + */ + public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException + { + HashMap map = new HashMap(); + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + if (name[0] != null) { + //System.out.println("Adding to map: " + name + " -> " + val); + map.put(name[0], val); + } else { + throw new XmlPullParserException( + "Map value without name attribute: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return map; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException + { + ArrayList list = new ArrayList(); + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + list.add(val); + //System.out.println("Adding to list: " + val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return list; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an int[] object from an XmlPullParser. The XML data could + * previously have been generated by writeIntArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated int[]. + * + * @see #readListXml + */ + public static final int[] readThisIntArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need num attribute in byte-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in num attribute in byte-array"); + } + + int[] array = new int[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException( + "Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a flattened object from an XmlPullParser. The XML data could + * previously have been written with writeMapXml(), writeListXml(), or + * writeValueXml(). The XmlPullParser must be positioned <em>at</em> the + * tag that defines the value. + * + * @param parser The XmlPullParser from which to read the object. + * @param name An array of one string, used to return the name attribute + * of the value's tag. + * + * @return Object The newly generated value object. + * + * @see #readMapXml + * @see #readListXml + * @see #writeValueXml + */ + public static final Object readValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException + { + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + return readThisValueXml(parser, name); + } else if (eventType == parser.END_TAG) { + throw new XmlPullParserException( + "Unexpected end tag at: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text: " + parser.getText()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Unexpected end of document"); + } + + private static final Object readThisValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException + { + final String valueName = parser.getAttributeValue(null, "name"); + final String tagName = parser.getName(); + + //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName); + + Object res; + + if (tagName.equals("null")) { + res = null; + } else if (tagName.equals("string")) { + String value = ""; + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals("string")) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + value); + return value; + } + throw new XmlPullParserException( + "Unexpected end tag in <string>: " + parser.getName()); + } else if (eventType == parser.TEXT) { + value += parser.getText(); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in <string>: " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in <string>"); + } else if (tagName.equals("int")) { + res = Integer.parseInt(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("long")) { + res = Long.valueOf(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("float")) { + res = new Float(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("double")) { + res = new Double(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("boolean")) { + res = Boolean.valueOf(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("int-array")) { + parser.next(); + res = readThisIntArrayXml(parser, "int-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("map")) { + parser.next(); + res = readThisMapXml(parser, "map", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("list")) { + parser.next(); + res = readThisListXml(parser, "list", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else { + throw new XmlPullParserException( + "Unknown tag: " + tagName); + } + + // Skip through to end tag. + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals(tagName)) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } + throw new XmlPullParserException( + "Unexpected end tag in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in <" + tagName + ">: " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in <" + tagName + ">"); + } + + public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + + if (type != parser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + + public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + } +} |