diff options
-rw-r--r-- | core/java/android/provider/Checkin.java | 5 | ||||
-rw-r--r-- | core/java/android/provider/Contacts.java | 24 | ||||
-rw-r--r-- | core/java/android/widget/ImageButton.java | 32 | ||||
-rw-r--r-- | core/java/com/google/android/net/GoogleHttpClient.java | 116 | ||||
-rw-r--r-- | tests/CoreTests/android/location/LocationManagerProximityTest.java | 5 |
5 files changed, 136 insertions, 46 deletions
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java index 3c23db0..f2c275e 100644 --- a/core/java/android/provider/Checkin.java +++ b/core/java/android/provider/Checkin.java @@ -137,6 +137,8 @@ public final class Checkin { CRASHES_TRUNCATED, ELAPSED_REALTIME_SEC, ELAPSED_UPTIME_SEC, + HTTP_REQUEST, + HTTP_REUSED, HTTP_STATUS, PHONE_GSM_REGISTERED, PHONE_GPRS_ATTEMPTED, @@ -351,6 +353,3 @@ public final class Checkin { } } } - - - diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index a6450f3..188cbcf 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -349,27 +349,33 @@ public class Contacts { } /** - * Adds a person to the My Contacts group. - * - * @param resolver the resolver to use - * @param personId the person to add to the group - * @return the URI of the group membership row - * @throws IllegalStateException if the My Contacts group can't be found + * @hide Used in vCard parser code. */ - public static Uri addToMyContactsGroup(ContentResolver resolver, long personId) { - long groupId = 0; + public static long tryGetMyContactsGroupId(ContentResolver resolver) { Cursor groupsCursor = resolver.query(Groups.CONTENT_URI, GROUPS_PROJECTION, Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null); if (groupsCursor != null) { try { if (groupsCursor.moveToFirst()) { - groupId = groupsCursor.getLong(0); + return groupsCursor.getLong(0); } } finally { groupsCursor.close(); } } + return 0; + } + /** + * Adds a person to the My Contacts group. + * + * @param resolver the resolver to use + * @param personId the person to add to the group + * @return the URI of the group membership row + * @throws IllegalStateException if the My Contacts group can't be found + */ + public static Uri addToMyContactsGroup(ContentResolver resolver, long personId) { + long groupId = tryGetMyContactsGroupId(resolver); if (groupId == 0) { throw new IllegalStateException("Failed to find the My Contacts group"); } diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java index 4c1cbf6..d417e40 100644 --- a/core/java/android/widget/ImageButton.java +++ b/core/java/android/widget/ImageButton.java @@ -27,9 +27,35 @@ import java.util.Map; /** * <p> - * An image button displays an image that can be pressed, or clicked, by the - * user. - * </p> + * Displays a button with an image (instead of text) that can be pressed + * or clicked by the user. By default, an ImageButton looks like a regular + * {@link android.widget.Button}, with the standard button background + * that changes color during different button states. The image on the surface + * of the button is defined either by the {@code android:src} attribute in the + * {@code <ImageButton>} XML element or by the + * {@link #setImageResource(int)} method.</p> + * + * <p>To remove the standard button background image, define your own + * background image or set the background color to be transparent.</p> + * <p>To indicate the different button states (focused, selected, etc.), you can + * define a different image for each state. E.g., a blue image by default, an + * orange one for when focused, and a yellow one for when pressed. An easy way to + * do this is with an XML drawable "selector." For example:</p> + * <pre> + * <?xml version="1.0" encoding="utf-8"?> + * <selector xmlns:android="http://schemas.android.com/apk/res/android"> + * <item android:drawable="@drawable/button_normal" /> <!-- default --> + * <item android:state_pressed="true" + * android:drawable="@drawable/button_pressed" /> <!-- pressed --> + * <item android:state_focused="true" + * android:drawable="@drawable/button_focused" /> <!-- focused --> + * </selector></pre> + * + * <p>Save the XML file in your project {@code res/drawable/} folder and then + * reference it as a drawable for the source of your ImageButton (in the + * {@code android:src} attribute). Android will automatically change the image + * based on the state of the button and the corresponding images + * defined in the XML.</p> * * <p><strong>XML attributes</strong></p> * <p> diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java index 871c925..25d0122 100644 --- a/core/java/com/google/android/net/GoogleHttpClient.java +++ b/core/java/com/google/android/net/GoogleHttpClient.java @@ -37,6 +37,10 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.LayeredSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.scheme.SocketFactory; import org.apache.http.impl.client.EntityEnclosingRequestWrapper; import org.apache.http.impl.client.RequestWrapper; import org.apache.http.params.HttpParams; @@ -44,6 +48,8 @@ import org.apache.http.protocol.HttpContext; import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; import java.net.URI; import java.net.URISyntaxException; @@ -66,25 +72,22 @@ public class GoogleHttpClient implements HttpClient { private final AndroidHttpClient mClient; private final ContentResolver mResolver; - private final String mUserAgent; + private final String mAppName, mUserAgent; + private final ThreadLocal mConnectionAllocated = new ThreadLocal<Boolean>(); /** - * Create an HTTP client. Normally one client is shared throughout an app. - * @param resolver to use for accessing URL rewriting rules. - * @param userAgent to report in your HTTP requests. - * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)} + * Create an HTTP client without SSL session persistence. + * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)} */ public GoogleHttpClient(ContentResolver resolver, String userAgent) { mClient = AndroidHttpClient.newInstance(userAgent); mResolver = resolver; - mUserAgent = userAgent; + mUserAgent = mAppName = userAgent; } /** - * GoogleHttpClient(Context, String, boolean) - without SSL session - * persistence. - * - * @deprecated use Context instead of ContentResolver. + * Create an HTTP client without SSL session persistence. + * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)} */ public GoogleHttpClient(ContentResolver resolver, String appAndVersion, boolean gzipCapable) { @@ -111,21 +114,70 @@ public class GoogleHttpClient implements HttpClient { * headers. Needed because Google servers require gzip in the User-Agent * in order to return gzip'd content. */ - public GoogleHttpClient(Context context, String appAndVersion, - boolean gzipCapable) { - this(context.getContentResolver(), SSLClientSessionCacheFactory.getCache(context), + public GoogleHttpClient(Context context, String appAndVersion, boolean gzipCapable) { + this(context.getContentResolver(), + SSLClientSessionCacheFactory.getCache(context), appAndVersion, gzipCapable); } - private GoogleHttpClient(ContentResolver resolver, SSLClientSessionCache cache, + private GoogleHttpClient(ContentResolver resolver, + SSLClientSessionCache cache, String appAndVersion, boolean gzipCapable) { String userAgent = appAndVersion + " (" + Build.DEVICE + " " + Build.ID + ")"; if (gzipCapable) { userAgent = userAgent + "; gzip"; } + mClient = AndroidHttpClient.newInstance(userAgent, cache); mResolver = resolver; + mAppName = appAndVersion; mUserAgent = userAgent; + + // Wrap all the socket factories with the appropriate wrapper. (Apache + // HTTP, curse its black and stupid heart, inspects the SocketFactory to + // see if it's a LayeredSocketFactory, so we need two wrapper classes.) + SchemeRegistry registry = getConnectionManager().getSchemeRegistry(); + for (String name : registry.getSchemeNames()) { + Scheme scheme = registry.unregister(name); + SocketFactory sf = scheme.getSocketFactory(); + if (sf instanceof LayeredSocketFactory) { + sf = new WrappedLayeredSocketFactory((LayeredSocketFactory) sf); + } else { + sf = new WrappedSocketFactory(sf); + } + registry.register(new Scheme(name, sf, scheme.getDefaultPort())); + } + } + + /** + * Delegating wrapper for SocketFactory records when sockets are connected. + * We use this to know whether a connection was created vs reused, to + * gather per-app statistics about connection reuse rates. + */ + private class WrappedSocketFactory implements SocketFactory { + private SocketFactory mDelegate; + private WrappedSocketFactory(SocketFactory delegate) { mDelegate = delegate; } + public final Socket createSocket() throws IOException { return mDelegate.createSocket(); } + public final boolean isSecure(Socket s) { return mDelegate.isSecure(s); } + + public final Socket connectSocket( + Socket s, String h, int p, + InetAddress la, int lp, HttpParams params) throws IOException { + mConnectionAllocated.set(Boolean.TRUE); + return mDelegate.connectSocket(s, h, p, la, lp, params); + } + } + + /** Like WrappedSocketFactory, but for the LayeredSocketFactory subclass. */ + private class WrappedLayeredSocketFactory + extends WrappedSocketFactory implements LayeredSocketFactory { + private LayeredSocketFactory mDelegate; + private WrappedLayeredSocketFactory(LayeredSocketFactory sf) { super(sf); mDelegate = sf; } + + public final Socket createSocket(Socket s, String host, int port, boolean autoClose) + throws IOException { + return mDelegate.createSocket(s, host, port, autoClose); + } } /** @@ -140,24 +192,21 @@ public class GoogleHttpClient implements HttpClient { public HttpResponse executeWithoutRewriting( HttpUriRequest request, HttpContext context) throws IOException { - String code = "Error"; + int code = -1; long start = SystemClock.elapsedRealtime(); try { HttpResponse response; - // TODO: if we're logging network stats, and if the apache library is configured - // to follow redirects, count each redirect as an additional round trip. + mConnectionAllocated.set(null); - // see if we're logging network stats. - boolean logNetworkStats = NetworkStatsEntity.shouldLogNetworkStats(); + if (NetworkStatsEntity.shouldLogNetworkStats()) { + // TODO: if we're logging network stats, and if the apache library is configured + // to follow redirects, count each redirect as an additional round trip. - if (logNetworkStats) { int uid = android.os.Process.myUid(); long startTx = NetStat.getUidTxBytes(uid); long startRx = NetStat.getUidRxBytes(uid); response = mClient.execute(request, context); - code = Integer.toString(response.getStatusLine().getStatusCode()); - HttpEntity origEntity = response == null ? null : response.getEntity(); if (origEntity != null) { // yeah, we compute the same thing below. we do need to compute this here @@ -165,30 +214,37 @@ public class GoogleHttpClient implements HttpClient { long now = SystemClock.elapsedRealtime(); long elapsed = now - start; NetworkStatsEntity entity = new NetworkStatsEntity(origEntity, - mUserAgent, uid, startTx, startRx, + mAppName, uid, startTx, startRx, elapsed /* response latency */, now /* processing start time */); response.setEntity(entity); } } else { response = mClient.execute(request, context); - code = Integer.toString(response.getStatusLine().getStatusCode()); } + code = response.getStatusLine().getStatusCode(); return response; - } catch (IOException e) { - code = "IOException"; - throw e; } finally { // Record some statistics to the checkin service about the outcome. // Note that this is only describing execute(), not body download. try { long elapsed = SystemClock.elapsedRealtime() - start; ContentValues values = new ContentValues(); - values.put(Checkin.Stats.TAG, - Checkin.Stats.Tag.HTTP_STATUS + ":" + - mUserAgent + ":" + code); values.put(Checkin.Stats.COUNT, 1); values.put(Checkin.Stats.SUM, elapsed / 1000.0); + + values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REQUEST + ":" + mAppName); + mResolver.insert(Checkin.Stats.CONTENT_URI, values); + + // No sockets and no exceptions means we successfully reused a connection + if (mConnectionAllocated.get() == null && code >= 0) { + values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REUSED + ":" + mAppName); + mResolver.insert(Checkin.Stats.CONTENT_URI, values); + } + + String status = code < 0 ? "IOException" : Integer.toString(code); + values.put(Checkin.Stats.TAG, + Checkin.Stats.Tag.HTTP_STATUS + ":" + mAppName + ":" + status); mResolver.insert(Checkin.Stats.CONTENT_URI, values); } catch (Exception e) { Log.e(TAG, "Error recording stats", e); diff --git a/tests/CoreTests/android/location/LocationManagerProximityTest.java b/tests/CoreTests/android/location/LocationManagerProximityTest.java index 3f43bcf..e82d878 100644 --- a/tests/CoreTests/android/location/LocationManagerProximityTest.java +++ b/tests/CoreTests/android/location/LocationManagerProximityTest.java @@ -26,6 +26,7 @@ import android.location.LocationManager; import android.provider.Settings; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; import android.util.Log; /** @@ -37,9 +38,11 @@ import android.util.Log; * adb shell am instrument -e class android.location.LocationManagerProximityTest \ * -w android.core/android.test.InstrumentationTestRunner * - * This test requires that the "Allow mock locations" setting be enabled + * This test requires that the "Allow mock locations" setting be enabled. + * To ensure reliable results, all location providers should be disabled. * */ +@Suppress @MediumTest public class LocationManagerProximityTest extends AndroidTestCase { |