Merge change 24805 into eclair

* changes:
  Don't init wifiChannels until after supplicant up
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index c211b47..c60045c 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -216,11 +216,9 @@
     } else
         vibrate_fd = -1;
 
-#if 0
     /* switch to non-root user and group */
     setgroups(sizeof(groups)/sizeof(groups[0]), groups);
     setuid(AID_SHELL);
-#endif
 
     /* make it safe to use both printf and STDOUT_FILENO */ 
     setvbuf(stdout, 0, _IONBF, 0);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 8c422a2..03346fe 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -185,7 +185,7 @@
 
         private final HandlerCaller mCaller;
 
-        private AccessibilityService mTarget;
+        private final AccessibilityService mTarget;
 
         public IEventListenerWrapper(AccessibilityService context) {
             mTarget = context;
@@ -211,9 +211,9 @@
             switch (message.what) {
                 case DO_ON_ACCESSIBILITY_EVENT :
                     AccessibilityEvent event = (AccessibilityEvent) message.obj;
-                    if (event != null){
-                      mTarget.onAccessibilityEvent(event);
-                      event.recycle();
+                    if (event != null) {
+                        mTarget.onAccessibilityEvent(event);
+                        event.recycle();
                     }
                     return;
                 case DO_ON_INTERRUPT :
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index d04abe5..9afeb74 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -21,11 +21,13 @@
 import android.content.Context;
 import android.content.IntentFilter;
 import android.content.BroadcastReceiver;
+import android.database.SQLException;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.Parcelable;
+import android.util.Log;
 
 import java.io.IOException;
 import java.util.concurrent.Callable;
@@ -364,7 +366,13 @@
         handler = (handler == null) ? mMainHandler : handler;
         handler.post(new Runnable() {
             public void run() {
-                listener.onAccountsUpdated(accountsCopy);
+                try {
+                    listener.onAccountsUpdated(accountsCopy);
+                } catch (SQLException e) {
+                    // Better luck next time.  If the problem was disk-full,
+                    // the STORAGE_OK intent will re-trigger the update.
+                    Log.e(TAG, "Can't update accounts", e);
+                }
             }
         });
     }
@@ -824,6 +832,8 @@
                 // Register a broadcast receiver to monitor account changes
                 IntentFilter intentFilter = new IntentFilter();
                 intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
+                // To recover from disk-full.
+                intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 
                 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
             }
         }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 6d79aee..b8c3aa3 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -24,6 +24,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.PerformanceCollector;
 import android.os.RemoteException;
 import android.os.Debug;
 import android.os.IBinder;
@@ -83,10 +84,8 @@
     private List<ActivityWaiter> mWaitingActivities;
     private List<ActivityMonitor> mActivityMonitors;
     private IInstrumentationWatcher mWatcher;
-    private long mPreCpuTime;
-    private long mStart;
     private boolean mAutomaticPerformanceSnapshots = false;
-    private Bundle mPrePerfMetrics = new Bundle();
+    private PerformanceCollector mPerformanceCollector;
     private Bundle mPerfMetrics = new Bundle();
 
     public Instrumentation() {
@@ -191,96 +190,21 @@
     
     public void setAutomaticPerformanceSnapshots() {
         mAutomaticPerformanceSnapshots = true;
+        mPerformanceCollector = new PerformanceCollector();
     }
 
     public void startPerformanceSnapshot() {
-        mStart = 0;
         if (!isProfiling()) {
-            // Add initial binder counts
-            Bundle binderCounts = getBinderCounts();
-            for (String key: binderCounts.keySet()) {
-                addPerfMetricLong("pre_" + key, binderCounts.getLong(key));
-            }
-
-            // Force a GC and zero out the performance counters.  Do this
-            // before reading initial CPU/wall-clock times so we don't include
-            // the cost of this setup in our final metrics.
-            startAllocCounting();
-
-            // Record CPU time up to this point, and start timing.  Note:  this
-            // must happen at the end of this method, otherwise the timing will
-            // include noise.
-            mStart = SystemClock.uptimeMillis();
-            mPreCpuTime = Process.getElapsedCpuTime();
+            mPerformanceCollector.beginSnapshot(null);
         }
     }
     
     public void endPerformanceSnapshot() {
         if (!isProfiling()) {
-            // Stop the timing. This must be done first before any other counting is stopped.
-            long cpuTime = Process.getElapsedCpuTime();
-            long duration = SystemClock.uptimeMillis();
-            
-            stopAllocCounting();
-            
-            long nativeMax = Debug.getNativeHeapSize() / 1024;
-            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
-            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
-
-            Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
-            Debug.getMemoryInfo(memInfo);
-
-            Runtime runtime = Runtime.getRuntime();
-
-            long dalvikMax = runtime.totalMemory() / 1024;
-            long dalvikFree = runtime.freeMemory() / 1024;
-            long dalvikAllocated = dalvikMax - dalvikFree;
-            
-            // Add final binder counts
-            Bundle binderCounts = getBinderCounts();
-            for (String key: binderCounts.keySet()) {
-                addPerfMetricLong(key, binderCounts.getLong(key));
-            }
-            
-            // Add alloc counts
-            Bundle allocCounts = getAllocCounts();
-            for (String key: allocCounts.keySet()) {
-                addPerfMetricLong(key, allocCounts.getLong(key));
-            }
-            
-            addPerfMetricLong("execution_time", duration - mStart);
-            addPerfMetricLong("pre_cpu_time", mPreCpuTime);
-            addPerfMetricLong("cpu_time", cpuTime - mPreCpuTime);
-
-            addPerfMetricLong("native_size", nativeMax);
-            addPerfMetricLong("native_allocated", nativeAllocated);
-            addPerfMetricLong("native_free", nativeFree);
-            addPerfMetricInt("native_pss", memInfo.nativePss);
-            addPerfMetricInt("native_private_dirty", memInfo.nativePrivateDirty);
-            addPerfMetricInt("native_shared_dirty", memInfo.nativeSharedDirty);
-            
-            addPerfMetricLong("java_size", dalvikMax);
-            addPerfMetricLong("java_allocated", dalvikAllocated);
-            addPerfMetricLong("java_free", dalvikFree);
-            addPerfMetricInt("java_pss", memInfo.dalvikPss);
-            addPerfMetricInt("java_private_dirty", memInfo.dalvikPrivateDirty);
-            addPerfMetricInt("java_shared_dirty", memInfo.dalvikSharedDirty);
-            
-            addPerfMetricInt("other_pss", memInfo.otherPss);
-            addPerfMetricInt("other_private_dirty", memInfo.otherPrivateDirty);
-            addPerfMetricInt("other_shared_dirty", memInfo.otherSharedDirty);
-            
+            mPerfMetrics = mPerformanceCollector.endSnapshot();
         }
     }
     
-    private void addPerfMetricLong(String key, long value) {
-        mPerfMetrics.putLong("performance." + key, value);
-    }
-    
-    private void addPerfMetricInt(String key, int value) {
-        mPerfMetrics.putInt("performance." + key, value);
-    }
-    
     /**
      * Called when the instrumented application is stopping, after all of the
      * normal application cleanup has occurred.
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index a9cec50..b1861ac 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -250,7 +250,7 @@
      * @hide */
     public static final int BOND_SUCCESS = 0;
     /** A bond attempt failed because pins did not match, or remote device did
-     * not respond to pin request in time 
+     * not respond to pin request in time
      * @hide */
     public static final int UNBOND_REASON_AUTH_FAILED = 1;
     /** A bond attempt failed because the other side explicilty rejected
@@ -266,9 +266,15 @@
     /** A bond attempt failed because a discovery is in progress
      * @hide */
     public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
+    /** A bond attempt failed because of authentication timeout
+     * @hide */
+    public static final int UNBOND_REASON_AUTH_TIMEOUT = 6;
+    /** A bond attempt failed because of repeated attempts
+     * @hide */
+    public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7;
     /** An existing bond was explicitly revoked
      * @hide */
-    public static final int UNBOND_REASON_REMOVED = 6;
+    public static final int UNBOND_REASON_REMOVED = 8;
 
     /** The user will be prompted to enter a pin
      * @hide */
@@ -278,7 +284,13 @@
     public static final int PAIRING_VARIANT_PASSKEY = 1;
     /** The user will be prompted to confirm the passkey displayed on the screen
      * @hide */
-    public static final int PAIRING_VARIANT_CONFIRMATION = 2;
+    public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
+    /** The user will be prompted to accept or deny the incoming pairing request
+     * @hide */
+    public static final int PAIRING_VARIANT_CONSENT = 3;
+    /** The user will be prompted to enter the passkey displayed on remote device
+     * @hide */
+    public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
 
     private static IBluetooth sService;  /* Guarenteed constant after first object constructed */
 
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 0bc8a9d..0d43b2a 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -24,7 +24,6 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Locale;
 
 /**
  * Provides access to an application's raw asset files; see {@link Resources}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7d412a7..ba5c9ed 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -66,8 +66,6 @@
             = new SparseArray<ColorStateList>();
     private static boolean mPreloaded;
 
-    private final LongSparseArray<Drawable.ConstantState> mPreloadedDrawables;
-
     /*package*/ final TypedValue mTmpValue = new TypedValue();
 
     // These are protected by the mTmpValue lock.
@@ -158,11 +156,6 @@
         }
         updateConfiguration(config, metrics);
         assets.ensureStringBlocks();
-        if (mCompatibilityInfo.isScalingRequired()) {
-            mPreloadedDrawables = emptySparseArray();
-        } else {
-            mPreloadedDrawables = sPreloadedDrawables;
-        }
     }
 
     /**
@@ -1669,7 +1662,7 @@
             return dr;
         }
 
-        Drawable.ConstantState cs = mPreloadedDrawables.get(key);
+        Drawable.ConstantState cs = sPreloadedDrawables.get(key);
         if (cs != null) {
             dr = cs.newDrawable();
         } else {
@@ -1976,7 +1969,6 @@
         mMetrics.setToDefaults();
         updateConfiguration(null, null);
         mAssets.ensureStringBlocks();
-        mPreloadedDrawables = sPreloadedDrawables;
         mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
     }
 }
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
index 5bfdcc4..30ecf5a 100755
--- a/core/java/android/gesture/GestureOverlayView.java
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -29,6 +29,7 @@
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 import android.os.SystemClock;
+import android.annotation.Widget;
 import com.android.internal.R;
 
 import java.util.ArrayList;
@@ -50,6 +51,7 @@
  * @attr ref android.R.styleable#GestureOverlayView_orientation
  * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
  */
+@Widget
 public class GestureOverlayView extends FrameLayout {
     public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
     public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
diff --git a/core/java/android/os/PerformanceCollector.java b/core/java/android/os/PerformanceCollector.java
new file mode 100644
index 0000000..4ca1f32
--- /dev/null
+++ b/core/java/android/os/PerformanceCollector.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+
+import java.util.ArrayList;
+
+/**
+ * Collects performance data between two function calls in Bundle objects and
+ * outputs the results using writer of type {@link PerformanceResultsWriter}.
+ * <p>
+ * {@link #beginSnapshot(String)} and {@link #endSnapshot()} functions collect
+ * memory usage information and measure runtime between calls to begin and end.
+ * These functions logically wrap around an entire test, and should be called
+ * with name of test as the label, e.g. EmailPerformanceTest.
+ * <p>
+ * {@link #startTiming(String)} and {@link #stopTiming(String)} functions
+ * measure runtime between calls to start and stop. These functions logically
+ * wrap around a single test case or a small block of code, and should be called
+ * with the name of test case as the label, e.g. testSimpleSendMailSequence.
+ * <p>
+ * {@link #addIteration(String)} inserts intermediate measurement point which
+ * can be labeled with a String, e.g. Launch email app, compose, send, etc.
+ * <p>
+ * Snapshot and timing functions do not interfere with each other, and thus can
+ * be called in any order. The intended structure is to wrap begin/endSnapshot
+ * around calls to start/stopTiming, for example:
+ * <p>
+ * <code>beginSnapshot("EmailPerformanceTest");
+ * startTiming("testSimpleSendSequence");
+ * addIteration("Launch email app");
+ * addIteration("Compose");
+ * stopTiming("Send");
+ * startTiming("testComplexSendSequence");
+ * stopTiming("");
+ * startTiming("testAddLabel");
+ * stopTiming("");
+ * endSnapshot();</code>
+ * <p>
+ * Structure of results output is up to implementor of
+ * {@link PerformanceResultsWriter }.
+ *
+ * {@hide} Pending approval for public API.
+ */
+public class PerformanceCollector {
+
+    /**
+     * Interface for reporting performance data.
+     */
+    public interface PerformanceResultsWriter {
+
+        /**
+         * Callback invoked as first action in
+         * PerformanceCollector#beginSnapshot(String) for reporting the start of
+         * a performance snapshot.
+         *
+         * @param label description of code block between beginSnapshot and
+         *              PerformanceCollector#endSnapshot()
+         * @see PerformanceCollector#beginSnapshot(String)
+         */
+        public void writeBeginSnapshot(String label);
+
+        /**
+         * Callback invoked as last action in PerformanceCollector#endSnapshot()
+         * for reporting performance data collected in the snapshot.
+         *
+         * @param results memory and runtime metrics stored as key/value pairs,
+         *        in the same structure as returned by
+         *        PerformanceCollector#endSnapshot()
+         * @see PerformanceCollector#endSnapshot()
+         */
+        public void writeEndSnapshot(Bundle results);
+
+        /**
+         * Callback invoked as first action in
+         * PerformanceCollector#startTiming(String) for reporting the start of
+         * a timing measurement.
+         *
+         * @param label description of code block between startTiming and
+         *              PerformanceCollector#stopTiming(String)
+         * @see PerformanceCollector#startTiming(String)
+         */
+        public void writeStartTiming(String label);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#stopTiming(String)} for reporting the
+         * sequence of timings measured.
+         *
+         * @param results runtime metrics of code block between calls to
+         *                startTiming and stopTiming, in the same structure as
+         *                returned by PerformanceCollector#stopTiming(String)
+         * @see PerformanceCollector#stopTiming(String)
+         */
+        public void writeStopTiming(Bundle results);
+    }
+
+    /**
+     * In a results Bundle, this key references a List of iteration Bundles.
+     */
+    public static final String METRIC_KEY_ITERATIONS = "iterations";
+    /**
+     * In an iteration Bundle, this key describes the iteration.
+     */
+    public static final String METRIC_KEY_LABEL = "label";
+    /**
+     * In a results Bundle, this key reports the cpu time of the code block
+     * under measurement.
+     */
+    public static final String METRIC_KEY_CPU_TIME = "cpu_time";
+    /**
+     * In a results Bundle, this key reports the execution time of the code
+     * block under measurement.
+     */
+    public static final String METRIC_KEY_EXECUTION_TIME = "execution_time";
+    /**
+     * In a snapshot Bundle, this key reports the number of received
+     * transactions from the binder driver before collection started.
+     */
+    public static final String METRIC_KEY_PRE_RECEIVED_TRANSACTIONS = "pre_received_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of transactions sent by
+     * the running program before collection started.
+     */
+    public static final String METRIC_KEY_PRE_SENT_TRANSACTIONS = "pre_sent_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of received
+     * transactions from the binder driver.
+     */
+    public static final String METRIC_KEY_RECEIVED_TRANSACTIONS = "received_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of transactions sent by
+     * the running program.
+     */
+    public static final String METRIC_KEY_SENT_TRANSACTIONS = "sent_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of garbage collection
+     * invocations.
+     */
+    public static final String METRIC_KEY_GC_INVOCATION_COUNT = "gc_invocation_count";
+    /**
+     * In a snapshot Bundle, this key reports the amount of allocated memory
+     * used by the running program.
+     */
+    public static final String METRIC_KEY_JAVA_ALLOCATED = "java_allocated";
+    /**
+     * In a snapshot Bundle, this key reports the amount of free memory
+     * available to the running program.
+     */
+    public static final String METRIC_KEY_JAVA_FREE = "java_free";
+    /**
+     * In a snapshot Bundle, this key reports the number of private dirty pages
+     * used by dalvik.
+     */
+    public static final String METRIC_KEY_JAVA_PRIVATE_DIRTY = "java_private_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the proportional set size for
+     * dalvik.
+     */
+    public static final String METRIC_KEY_JAVA_PSS = "java_pss";
+    /**
+     * In a snapshot Bundle, this key reports the number of shared dirty pages
+     * used by dalvik.
+     */
+    public static final String METRIC_KEY_JAVA_SHARED_DIRTY = "java_shared_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the total amount of memory
+     * available to the running program.
+     */
+    public static final String METRIC_KEY_JAVA_SIZE = "java_size";
+    /**
+     * In a snapshot Bundle, this key reports the amount of allocated memory in
+     * the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_ALLOCATED = "native_allocated";
+    /**
+     * In a snapshot Bundle, this key reports the amount of free memory in the
+     * native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_FREE = "native_free";
+    /**
+     * In a snapshot Bundle, this key reports the number of private dirty pages
+     * used by the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_PRIVATE_DIRTY = "native_private_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the proportional set size for the
+     * native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_PSS = "native_pss";
+    /**
+     * In a snapshot Bundle, this key reports the number of shared dirty pages
+     * used by the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_SHARED_DIRTY = "native_shared_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the size of the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_SIZE = "native_size";
+    /**
+     * In a snapshot Bundle, this key reports the number of objects allocated
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_ALLOC_COUNT = "global_alloc_count";
+    /**
+     * In a snapshot Bundle, this key reports the size of all objects allocated
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_ALLOC_SIZE = "global_alloc_size";
+    /**
+     * In a snapshot Bundle, this key reports the number of objects freed
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_FREED_COUNT = "global_freed_count";
+    /**
+     * In a snapshot Bundle, this key reports the size of all objects freed
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_FREED_SIZE = "global_freed_size";
+    /**
+     * In a snapshot Bundle, this key reports the number of private dirty pages
+     * used by everything else.
+     */
+    public static final String METRIC_KEY_OTHER_PRIVATE_DIRTY = "other_private_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the proportional set size for
+     * everything else.
+     */
+    public static final String METRIC_KEY_OTHER_PSS = "other_pss";
+    /**
+     * In a snapshot Bundle, this key reports the number of shared dirty pages
+     * used by everything else.
+     */
+    public static final String METRIC_KEY_OTHER_SHARED_DIRTY = "other_shared_dirty";
+
+    private PerformanceResultsWriter mPerfWriter;
+    private Bundle mPerfSnapshot;
+    private Bundle mPerfMeasurement;
+    private long mSnapshotCpuTime;
+    private long mSnapshotExecTime;
+    private long mCpuTime;
+    private long mExecTime;
+
+    public PerformanceCollector() {
+    }
+
+    public PerformanceCollector(PerformanceResultsWriter writer) {
+        setPerformanceResultsWriter(writer);
+    }
+
+    public void setPerformanceResultsWriter(PerformanceResultsWriter writer) {
+        mPerfWriter = writer;
+    }
+
+    /**
+     * Begin collection of memory usage information.
+     *
+     * @param label description of code block between beginSnapshot and
+     *              endSnapshot, used to label output
+     */
+    public void beginSnapshot(String label) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeBeginSnapshot(label);
+        startPerformanceSnapshot();
+    }
+
+    /**
+     * End collection of memory usage information. Returns collected data in a
+     * Bundle object.
+     *
+     * @return Memory and runtime metrics stored as key/value pairs. Values are
+     *         of type long, and keys include:
+     *         <ul>
+     *         <li>{@link #METRIC_KEY_CPU_TIME cpu_time}
+     *         <li>{@link #METRIC_KEY_EXECUTION_TIME execution_time}
+     *         <li>{@link #METRIC_KEY_PRE_RECEIVED_TRANSACTIONS
+     *         pre_received_transactions}
+     *         <li>{@link #METRIC_KEY_PRE_SENT_TRANSACTIONS
+     *         pre_sent_transactions}
+     *         <li>{@link #METRIC_KEY_RECEIVED_TRANSACTIONS
+     *         received_transactions}
+     *         <li>{@link #METRIC_KEY_SENT_TRANSACTIONS sent_transactions}
+     *         <li>{@link #METRIC_KEY_GC_INVOCATION_COUNT gc_invocation_count}
+     *         <li>{@link #METRIC_KEY_JAVA_ALLOCATED java_allocated}
+     *         <li>{@link #METRIC_KEY_JAVA_FREE java_free}
+     *         <li>{@link #METRIC_KEY_JAVA_PRIVATE_DIRTY java_private_dirty}
+     *         <li>{@link #METRIC_KEY_JAVA_PSS java_pss}
+     *         <li>{@link #METRIC_KEY_JAVA_SHARED_DIRTY java_shared_dirty}
+     *         <li>{@link #METRIC_KEY_JAVA_SIZE java_size}
+     *         <li>{@link #METRIC_KEY_NATIVE_ALLOCATED native_allocated}
+     *         <li>{@link #METRIC_KEY_NATIVE_FREE native_free}
+     *         <li>{@link #METRIC_KEY_NATIVE_PRIVATE_DIRTY native_private_dirty}
+     *         <li>{@link #METRIC_KEY_NATIVE_PSS native_pss}
+     *         <li>{@link #METRIC_KEY_NATIVE_SHARED_DIRTY native_shared_dirty}
+     *         <li>{@link #METRIC_KEY_NATIVE_SIZE native_size}
+     *         <li>{@link #METRIC_KEY_GLOBAL_ALLOC_COUNT global_alloc_count}
+     *         <li>{@link #METRIC_KEY_GLOBAL_ALLOC_SIZE global_alloc_size}
+     *         <li>{@link #METRIC_KEY_GLOBAL_FREED_COUNT global_freed_count}
+     *         <li>{@link #METRIC_KEY_GLOBAL_FREED_SIZE global_freed_size}
+     *         <li>{@link #METRIC_KEY_OTHER_PRIVATE_DIRTY other_private_dirty}
+     *         <li>{@link #METRIC_KEY_OTHER_PSS other_pss}
+     *         <li>{@link #METRIC_KEY_OTHER_SHARED_DIRTY other_shared_dirty}
+     *         </ul>
+     */
+    public Bundle endSnapshot() {
+        endPerformanceSnapshot();
+        if (mPerfWriter != null)
+            mPerfWriter.writeEndSnapshot(mPerfSnapshot);
+        return mPerfSnapshot;
+    }
+
+    /**
+     * Start measurement of user and cpu time.
+     *
+     * @param label description of code block between startTiming and
+     *        stopTiming, used to label output
+     */
+    public void startTiming(String label) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeStartTiming(label);
+        mPerfMeasurement = new Bundle();
+        mPerfMeasurement.putParcelableArrayList(
+                METRIC_KEY_ITERATIONS, new ArrayList<Parcelable>());
+        mExecTime = SystemClock.uptimeMillis();
+        mCpuTime = Process.getElapsedCpuTime();
+    }
+
+    /**
+     * Add a measured segment, and start measuring the next segment. Returns
+     * collected data in a Bundle object.
+     *
+     * @param label description of code block between startTiming and
+     *              addIteration, and between two calls to addIteration, used
+     *              to label output
+     * @return Runtime metrics stored as key/value pairs. Values are of type
+     *         long, and keys include:
+     *         <ul>
+     *         <li>{@link #METRIC_KEY_LABEL label}
+     *         <li>{@link #METRIC_KEY_CPU_TIME cpu_time}
+     *         <li>{@link #METRIC_KEY_EXECUTION_TIME execution_time}
+     *         </ul>
+     */
+    public Bundle addIteration(String label) {
+        mCpuTime = Process.getElapsedCpuTime() - mCpuTime;
+        mExecTime = SystemClock.uptimeMillis() - mExecTime;
+
+        Bundle iteration = new Bundle();
+        iteration.putString(METRIC_KEY_LABEL, label);
+        iteration.putLong(METRIC_KEY_EXECUTION_TIME, mExecTime);
+        iteration.putLong(METRIC_KEY_CPU_TIME, mCpuTime);
+        mPerfMeasurement.getParcelableArrayList(METRIC_KEY_ITERATIONS).add(iteration);
+
+        mExecTime = SystemClock.uptimeMillis();
+        mCpuTime = Process.getElapsedCpuTime();
+        return iteration;
+    }
+
+    /**
+     * Stop measurement of user and cpu time.
+     *
+     * @param label description of code block between addIteration or
+     *              startTiming and stopTiming, used to label output
+     * @return Runtime metrics stored in a bundle, including all iterations
+     *         between calls to startTiming and stopTiming. List of iterations
+     *         is keyed by {@link #METRIC_KEY_ITERATIONS iterations}.
+     */
+    public Bundle stopTiming(String label) {
+        addIteration(label);
+        if (mPerfWriter != null)
+            mPerfWriter.writeStopTiming(mPerfMeasurement);
+        return mPerfMeasurement;
+    }
+
+    /*
+     * Starts tracking memory usage, binder transactions, and real & cpu timing.
+     */
+    private void startPerformanceSnapshot() {
+        // Create new snapshot
+        mPerfSnapshot = new Bundle();
+
+        // Add initial binder counts
+        Bundle binderCounts = getBinderCounts();
+        for (String key : binderCounts.keySet()) {
+            mPerfSnapshot.putLong("pre_" + key, binderCounts.getLong(key));
+        }
+
+        // Force a GC and zero out the performance counters. Do this
+        // before reading initial CPU/wall-clock times so we don't include
+        // the cost of this setup in our final metrics.
+        startAllocCounting();
+
+        // Record CPU time up to this point, and start timing. Note: this
+        // must happen at the end of this method, otherwise the timing will
+        // include noise.
+        mSnapshotExecTime = SystemClock.uptimeMillis();
+        mSnapshotCpuTime = Process.getElapsedCpuTime();
+    }
+
+    /*
+     * Stops tracking memory usage, binder transactions, and real & cpu timing.
+     * Stores collected data as type long into Bundle object for reporting.
+     */
+    private void endPerformanceSnapshot() {
+        // Stop the timing. This must be done first before any other counting is
+        // stopped.
+        mSnapshotCpuTime = Process.getElapsedCpuTime() - mSnapshotCpuTime;
+        mSnapshotExecTime = SystemClock.uptimeMillis() - mSnapshotExecTime;
+
+        stopAllocCounting();
+
+        long nativeMax = Debug.getNativeHeapSize() / 1024;
+        long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+        long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+        Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
+        Debug.getMemoryInfo(memInfo);
+
+        Runtime runtime = Runtime.getRuntime();
+
+        long dalvikMax = runtime.totalMemory() / 1024;
+        long dalvikFree = runtime.freeMemory() / 1024;
+        long dalvikAllocated = dalvikMax - dalvikFree;
+
+        // Add final binder counts
+        Bundle binderCounts = getBinderCounts();
+        for (String key : binderCounts.keySet()) {
+            mPerfSnapshot.putLong(key, binderCounts.getLong(key));
+        }
+
+        // Add alloc counts
+        Bundle allocCounts = getAllocCounts();
+        for (String key : allocCounts.keySet()) {
+            mPerfSnapshot.putLong(key, allocCounts.getLong(key));
+        }
+
+        mPerfSnapshot.putLong(METRIC_KEY_EXECUTION_TIME, mSnapshotExecTime);
+        mPerfSnapshot.putLong(METRIC_KEY_CPU_TIME, mSnapshotCpuTime);
+
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_SIZE, nativeMax);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_ALLOCATED, nativeAllocated);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_FREE, nativeFree);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_PSS, memInfo.nativePss);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_PRIVATE_DIRTY, memInfo.nativePrivateDirty);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_SHARED_DIRTY, memInfo.nativeSharedDirty);
+
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_SIZE, dalvikMax);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_ALLOCATED, dalvikAllocated);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_FREE, dalvikFree);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_PSS, memInfo.dalvikPss);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_PRIVATE_DIRTY, memInfo.dalvikPrivateDirty);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_SHARED_DIRTY, memInfo.dalvikSharedDirty);
+
+        mPerfSnapshot.putLong(METRIC_KEY_OTHER_PSS, memInfo.otherPss);
+        mPerfSnapshot.putLong(METRIC_KEY_OTHER_PRIVATE_DIRTY, memInfo.otherPrivateDirty);
+        mPerfSnapshot.putLong(METRIC_KEY_OTHER_SHARED_DIRTY, memInfo.otherSharedDirty);
+    }
+
+    /*
+     * Starts allocation counting. This triggers a gc and resets the counts.
+     */
+    private static void startAllocCounting() {
+        // Before we start trigger a GC and reset the debug counts. Run the
+        // finalizers and another GC before starting and stopping the alloc
+        // counts. This will free up any objects that were just sitting around
+        // waiting for their finalizers to be run.
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+
+        Debug.resetAllCounts();
+
+        // start the counts
+        Debug.startAllocCounting();
+    }
+
+    /*
+     * Stops allocation counting.
+     */
+    private static void stopAllocCounting() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        Debug.stopAllocCounting();
+    }
+
+    /*
+     * Returns a bundle with the current results from the allocation counting.
+     */
+    private static Bundle getAllocCounts() {
+        Bundle results = new Bundle();
+        results.putLong(METRIC_KEY_GLOBAL_ALLOC_COUNT, Debug.getGlobalAllocCount());
+        results.putLong(METRIC_KEY_GLOBAL_ALLOC_SIZE, Debug.getGlobalAllocSize());
+        results.putLong(METRIC_KEY_GLOBAL_FREED_COUNT, Debug.getGlobalFreedCount());
+        results.putLong(METRIC_KEY_GLOBAL_FREED_SIZE, Debug.getGlobalFreedSize());
+        results.putLong(METRIC_KEY_GC_INVOCATION_COUNT, Debug.getGlobalGcInvocationCount());
+        return results;
+    }
+
+    /*
+     * Returns a bundle with the counts for various binder counts for this
+     * process. Currently the only two that are reported are the number of send
+     * and the number of received transactions.
+     */
+    private static Bundle getBinderCounts() {
+        Bundle results = new Bundle();
+        results.putLong(METRIC_KEY_SENT_TRANSACTIONS, Debug.getBinderSentTransactions());
+        results.putLong(METRIC_KEY_RECEIVED_TRANSACTIONS, Debug.getBinderReceivedTransactions());
+        return results;
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 97955ae..688f377 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2401,11 +2401,6 @@
             WIFI_NUM_ALLOWED_CHANNELS,
             WIFI_NUM_OPEN_NETWORKS_KEPT,
             BACKGROUND_DATA,
-            PREFERRED_NETWORK_MODE,
-            PREFERRED_TTY_MODE,
-            CDMA_CELL_BROADCAST_SMS,
-            PREFERRED_CDMA_SUBSCRIPTION,
-            ENHANCED_VOICE_PRIVACY_ENABLED
         };
 
         /**
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 4c24c50..1ed5c49 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -17,8 +17,8 @@
 package android.server;
 
 import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
@@ -53,6 +53,7 @@
 
     private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
     private static final int EVENT_RESTART_BLUETOOTH = 2;
+    private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3;
 
     // The time (in millisecs) to delay the pairing attempt after the first
     // auto pairing attempt fails. We use an exponential delay with
@@ -67,9 +68,10 @@
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
+            String address = null;
             switch (msg.what) {
             case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
-                String address = (String)msg.obj;
+                address = (String)msg.obj;
                 if (address != null) {
                     mBluetoothService.createBond(address);
                     return;
@@ -78,6 +80,12 @@
             case EVENT_RESTART_BLUETOOTH:
                 mBluetoothService.restart();
                 break;
+            case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT:
+                address = (String)msg.obj;
+                if (address != null) {
+                    mBluetoothService.setPairingConfirmation(address, true);
+                }
+                break;
             }
         }
     };
@@ -166,6 +174,17 @@
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
     }
 
+    private void onDeviceDisconnectRequested(String deviceObjectPath) {
+        String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+        if (address == null) {
+            Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null");
+            return;
+        }
+        Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
     private void onCreatePairedDeviceResult(String address, int result) {
         address = address.toUpperCase();
         if (result == BluetoothDevice.BOND_SUCCESS) {
@@ -228,6 +247,7 @@
                 addDevice(address, properties);
             }
         }
+        mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING);
         return;
     }
 
@@ -379,7 +399,32 @@
         return address;
     }
 
-    private void onRequestConfirmation(String objectPath, int passkey, int nativeData) {
+    private void onRequestPairingConsent(String objectPath, int nativeData) {
+        String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
+        if (address == null) return;
+
+        /* The link key will not be stored if the incoming request has MITM
+         * protection switched on. Unfortunately, some devices have MITM
+         * switched on even though their capabilities are NoInputNoOutput,
+         * so we may get this request many times. Also if we respond immediately,
+         * the other end is unable to handle it. Delay sending the message.
+         */
+        if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) {
+            Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
+            message.obj = address;
+            mHandler.sendMessageDelayed(message, 1500);
+            return;
+        }
+
+        Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
+        intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
+                        BluetoothDevice.PAIRING_VARIANT_CONSENT);
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+        return;
+    }
+
+    private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) {
         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
         if (address == null) return;
 
@@ -387,7 +432,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
         intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey);
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
-                BluetoothDevice.PAIRING_VARIANT_CONFIRMATION);
+                BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
         return;
     }
@@ -436,6 +481,18 @@
         return;
     }
 
+    private void onDisplayPasskey(String objectPath, int passkey, int nativeData) {
+        String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
+        if (address == null) return;
+
+        Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
+        intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey);
+        intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
+                        BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY);
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+    }
+
     private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
         if (address == null) {
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 53fb03c..6ce0f5f 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -1266,5 +1266,4 @@
     private native boolean setPairingConfirmationNative(String address, boolean confirm,
             int nativeData);
     private native boolean setDevicePropertyBooleanNative(String objectPath, String key, int value);
-
 }
diff --git a/core/java/android/test/TimedTest.java b/core/java/android/test/TimedTest.java
new file mode 100644
index 0000000..3a60a25
--- /dev/null
+++ b/core/java/android/test/TimedTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation can be used on an {@link junit.framework.TestCase}'s test
+ * methods. When the annotation is present, the test method is timed and the
+ * results written through instrumentation output. It can also be used on the
+ * class itself, which is equivalent to tagging all test methods with this
+ * annotation.
+ *
+ * {@hide} Pending approval for public API.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TimedTest { }
\ No newline at end of file
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2f17bbc..f63c2f1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6038,16 +6038,23 @@
      * some form of this public, but should think about the API.
      */
     Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor) {
-        final int width = mRight - mLeft;
-        final int height = mBottom - mTop;
+        int width = mRight - mLeft;
+        int height = mBottom - mTop;
 
-        Bitmap bitmap = Bitmap.createBitmap(width, height, quality);
+        final AttachInfo attachInfo = mAttachInfo;
+        final float scale = attachInfo.mApplicationScale;
+        width = (int) ((width * scale) + 0.5f);
+        height = (int) ((height * scale) + 0.5f);
+        
+        Bitmap bitmap = Bitmap.createBitmap(width > 0 ? width : 1,
+                height > 0 ? height : 1, quality);
         if (bitmap == null) {
             throw new OutOfMemoryError();
         }
 
+        bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
+        
         Canvas canvas;
-        final AttachInfo attachInfo = mAttachInfo;
         if (attachInfo != null) {
             canvas = attachInfo.mCanvas;
             if (canvas == null) {
@@ -6070,6 +6077,7 @@
 
         computeScroll();
         final int restoreCount = canvas.save();
+        canvas.scale(scale, scale);
         canvas.translate(-mScrollX, -mScrollY);
 
         // Temporarily remove the dirty mask
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a611d5a..e98fd13 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -732,7 +732,11 @@
             }
         } else if (digits != null) {
             mInput = DigitsKeyListener.getInstance(digits.toString());
-            mInputType = inputType;
+            // If no input type was specified, we will default to generic
+            // text, since we can't tell the IME about the set of digits
+            // that was selected.
+            mInputType = inputType != EditorInfo.TYPE_NULL
+                    ? inputType : EditorInfo.TYPE_CLASS_TEXT;
         } else if (inputType != EditorInfo.TYPE_NULL) {
             setInputType(inputType, true);
             singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index fd5271e..50df0f7 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -1,4 +1,25 @@
+/*
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "9patch"
+#define LOG_NDEBUG 1
+
 #include <utils/ResourceTypes.h>
+#include <utils/Log.h>
 
 #include "SkCanvas.h"
 #include "SkRegion.h"
@@ -62,6 +83,9 @@
             
             if (destDensity == srcDensity || destDensity == 0
                     || srcDensity == 0) {
+                LOGV("Drawing unscaled 9-patch: (%g,%g)-(%g,%g)",
+                        SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
+                        SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom));
                 NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
             } else {
                 canvas->save();
@@ -74,6 +98,11 @@
                 bounds.fBottom = SkScalarDiv(bounds.fBottom-bounds.fTop, scale);
                 bounds.fLeft = bounds.fTop = 0;
     
+                LOGV("Drawing scaled 9-patch: (%g,%g)-(%g,%g) srcDensity=%d destDensity=%d",
+                        SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
+                        SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom),
+                        srcDensity, destDensity);
+                
                 NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
     
                 canvas->restore();
diff --git a/core/jni/android/graphics/NinePatchImpl.cpp b/core/jni/android/graphics/NinePatchImpl.cpp
index 32d9b57..ff24a87 100644
--- a/core/jni/android/graphics/NinePatchImpl.cpp
+++ b/core/jni/android/graphics/NinePatchImpl.cpp
@@ -16,8 +16,10 @@
 */
 
 #define LOG_TAG "NinePatch"
+#define LOG_NDEBUG 1
 
 #include <utils/ResourceTypes.h>
+#include <utils/Log.h>
 
 #include "SkBitmap.h"
 #include "SkCanvas.h"
@@ -25,7 +27,7 @@
 #include "SkPaint.h"
 #include "SkUnPreMultiply.h"
 
-#define USE_TRACEx
+#define USE_TRACE
 
 #ifdef USE_TRACE
     static bool gTrace;
@@ -130,10 +132,10 @@
 
     SkASSERT(canvas || outRegion);
 
-#if 0
+#ifdef USE_TRACE
     if (canvas) {
         const SkMatrix& m = canvas->getTotalMatrix();
-        SkDebugf("ninepatch [%g %g %g] [%g %g %g]\n",
+        LOGV("ninepatch [%g %g %g] [%g %g %g]\n",
                  SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
                  SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
     }
@@ -141,10 +143,10 @@
 
 #ifdef USE_TRACE
     if (gTrace) {
-        SkDEBUGF(("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height())));
-        SkDEBUGF(("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height()));
-        SkDEBUGF(("======== ninepatch xDivs [%d,%d]\n", chunk.xDivs[0], chunk.xDivs[1]));
-        SkDEBUGF(("======== ninepatch yDivs [%d,%d]\n", chunk.yDivs[0], chunk.yDivs[1]));
+        LOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()));
+        LOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
+        LOGV("======== ninepatch xDivs [%d,%d]\n", chunk.xDivs[0], chunk.xDivs[1]);
+        LOGV("======== ninepatch yDivs [%d,%d]\n", chunk.yDivs[0], chunk.yDivs[1]);
     }
 #endif
 
@@ -153,7 +155,7 @@
         (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
     {
 #ifdef USE_TRACE
-        if (gTrace) SkDEBUGF(("======== abort ninepatch draw\n"));
+        if (gTrace) LOGV("======== abort ninepatch draw\n");
 #endif
         return;
     }
@@ -198,8 +200,8 @@
     }
     int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
 
-#if 0
-    SkDebugf("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
+#ifdef USE_TRACE
+    LOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
              bitmap.width(), bitmap.height(),
              SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
              SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
@@ -302,13 +304,13 @@
                 goto nextDiv;
             }
             if (canvas) {
-#if 0
-                SkDebugf("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
+#ifdef USE_TRACE
+                LOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
                          src.fLeft, src.fTop, src.width(), src.height(),
                          SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
                          SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
                 if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
-                    SkDebugf("--- skip patch\n");
+                    LOGV("--- skip patch\n");
                 }
 #endif
                 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 8fe7487..e703ed8 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -45,13 +45,16 @@
 static jmethodID method_onDeviceDisappeared;
 static jmethodID method_onDeviceCreated;
 static jmethodID method_onDeviceRemoved;
+static jmethodID method_onDeviceDisconnectRequested;
 
 static jmethodID method_onCreatePairedDeviceResult;
 static jmethodID method_onGetDeviceServiceChannelResult;
 
 static jmethodID method_onRequestPinCode;
 static jmethodID method_onRequestPasskey;
-static jmethodID method_onRequestConfirmation;
+static jmethodID method_onRequestPasskeyConfirmation;
+static jmethodID method_onRequestPairingConsent;
+static jmethodID method_onDisplayPasskey;
 static jmethodID method_onAgentAuthorize;
 static jmethodID method_onAgentCancel;
 
@@ -84,6 +87,8 @@
                                                   "(Ljava/lang/String;)V");
     method_onDeviceCreated = env->GetMethodID(clazz, "onDeviceCreated", "(Ljava/lang/String;)V");
     method_onDeviceRemoved = env->GetMethodID(clazz, "onDeviceRemoved", "(Ljava/lang/String;)V");
+    method_onDeviceDisconnectRequested = env->GetMethodID(clazz, "onDeviceDisconnectRequested",
+                                                        "(Ljava/lang/String;)V");
 
     method_onCreatePairedDeviceResult = env->GetMethodID(clazz, "onCreatePairedDeviceResult",
                                                          "(Ljava/lang/String;I)V");
@@ -95,7 +100,11 @@
                                                "(Ljava/lang/String;I)V");
     method_onRequestPasskey = env->GetMethodID(clazz, "onRequestPasskey",
                                                "(Ljava/lang/String;I)V");
-    method_onRequestConfirmation = env->GetMethodID(clazz, "onRequestConfirmation",
+    method_onRequestPasskeyConfirmation = env->GetMethodID(clazz, "onRequestPasskeyConfirmation",
+                                               "(Ljava/lang/String;II)V");
+    method_onRequestPairingConsent = env->GetMethodID(clazz, "onRequestPairingConsent",
+                                               "(Ljava/lang/String;I)V");
+    method_onDisplayPasskey = env->GetMethodID(clazz, "onDisplayPasskey",
                                                "(Ljava/lang/String;II)V");
 
     field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I");
@@ -227,7 +236,7 @@
 
 
 const char * get_adapter_path(DBusConnection *conn) {
-    DBusMessage *msg, *reply = NULL;
+    DBusMessage *msg = NULL, *reply = NULL;
     DBusError err;
     const char *device_path = NULL;
     int attempt = 0;
@@ -815,6 +824,14 @@
                             str_array);
         } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
         goto success;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Device",
+                                      "DisconnectRequested")) {
+        const char *remote_device_path = dbus_message_get_path(msg);
+        env->CallVoidMethod(nat->me,
+                            method_onDeviceDisconnectRequested,
+                            env->NewStringUTF(remote_device_path));
+        goto success;
     }
 
     ret = a2dp_event_filter(msg, env);
@@ -845,9 +862,7 @@
 
     if (dbus_message_is_method_call(msg,
             "org.bluez.Agent", "Cancel")) {
-
         env->CallVoidMethod(nat->me, method_onAgentCancel);
-
         // reply
         DBusMessage *reply = dbus_message_new_method_return(msg);
         if (!reply) {
@@ -928,6 +943,24 @@
                                        int(msg));
         goto success;
     } else if (dbus_message_is_method_call(msg,
+            "org.bluez.Agent", "DisplayPasskey")) {
+        char *object_path;
+        uint32_t passkey;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_OBJECT_PATH, &object_path,
+                                   DBUS_TYPE_UINT32, &passkey,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for RequestPasskey() method", __FUNCTION__);
+            goto failure;
+        }
+
+        dbus_message_ref(msg);  // increment refcount because we pass to java
+        env->CallVoidMethod(nat->me, method_onDisplayPasskey,
+                                       env->NewStringUTF(object_path),
+                                       passkey,
+                                       int(msg));
+        goto success;
+    } else if (dbus_message_is_method_call(msg,
             "org.bluez.Agent", "RequestConfirmation")) {
         char *object_path;
         uint32_t passkey;
@@ -940,12 +973,27 @@
         }
 
         dbus_message_ref(msg);  // increment refcount because we pass to java
-        env->CallVoidMethod(nat->me, method_onRequestConfirmation,
+        env->CallVoidMethod(nat->me, method_onRequestPasskeyConfirmation,
                                        env->NewStringUTF(object_path),
                                        passkey,
                                        int(msg));
         goto success;
     } else if (dbus_message_is_method_call(msg,
+            "org.bluez.Agent", "RequestPairingConsent")) {
+        char *object_path;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_OBJECT_PATH, &object_path,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for RequestPairingConsent() method", __FUNCTION__);
+            goto failure;
+        }
+
+        dbus_message_ref(msg);  // increment refcount because we pass to java
+        env->CallVoidMethod(nat->me, method_onRequestPairingConsent,
+                                       env->NewStringUTF(object_path),
+                                       int(msg));
+        goto success;
+    } else if (dbus_message_is_method_call(msg,
                   "org.bluez.Agent", "Release")) {
         // reply
         DBusMessage *reply = dbus_message_new_method_return(msg);
@@ -981,6 +1029,8 @@
 #define BOND_RESULT_AUTH_CANCELED 3
 #define BOND_RESULT_REMOTE_DEVICE_DOWN 4
 #define BOND_RESULT_DISCOVERY_IN_PROGRESS 5
+#define BOND_RESULT_AUTH_TIMEOUT 6
+#define BOND_RESULT_REPEATED_ATTEMPTS 7
 
 void onCreatePairedDeviceResult(DBusMessage *msg, void *user, void *n) {
     LOGV(__FUNCTION__);
@@ -1026,6 +1076,12 @@
                    !strcmp(err.message, "Discover in progress")) {
             LOGV("... error = %s (%s)\n", err.name, err.message);
             result = BOND_RESULT_DISCOVERY_IN_PROGRESS;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.RepeatedAttempts")) {
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_REPEATED_ATTEMPTS;
+        } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationTimeout")) {
+            LOGV("... error = %s (%s)\n", err.name, err.message);
+            result = BOND_RESULT_AUTH_TIMEOUT;
         } else {
             LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message);
             result = BOND_RESULT_ERROR;
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index de921f1..0b71acb 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -455,8 +455,8 @@
         }
 
         if (!reply) {
-            LOGE("%s: Cannot create message reply to RequestConfirmation to "
-                 "D-Bus\n", __FUNCTION__);
+            LOGE("%s: Cannot create message reply to RequestPasskeyConfirmation or"
+                  "RequestPairingConsent to D-Bus\n", __FUNCTION__);
             dbus_message_unref(msg);
             return JNI_FALSE;
         }
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 924e19e..997efb8 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -21,6 +21,7 @@
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.TypedValue;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -170,6 +171,11 @@
 
     @Override
     public void draw(Canvas canvas) {
+        if (false) {
+            float[] pts = new float[2];
+            canvas.getMatrix().mapPoints(pts);
+            Log.v("9patch", "Drawing 9-patch @ " + pts[0] + "," + pts[1] + ": " + getBounds());
+        }
         mNinePatch.draw(canvas, getBounds(), mPaint);
     }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index 45a5e53..a86ef8f9 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -415,7 +415,7 @@
           null, null, "231180", "1", null},
       {"/sdcard/media_api/metaDataTestMedias/M4A/Jaws Of Life_ver1.m4a", "1/8", "Suspended Animation",
           "John Petrucci", null, null, "20070510T125223.000Z", 
-          "13", "Jaws Of Life", "2005", "19815424", "1", "m4a composer"},
+          "13", "Jaws Of Life", "2005", "449329", "1", "m4a composer"},
       {"/sdcard/media_api/metaDataTestMedias/M4V/sample_iPod.m4v", null, null, 
           null, null, null, "20051220T202015.000Z", 
           null, null, null, "85500", "2", null},
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
index 5e213d7..3667fae 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
@@ -97,6 +97,7 @@
         int video_duration = MediaNames.VIDEO_H263_AAC_DURATION;
         int random_play_time = 0;
         int random_seek_time = 0;
+        int random_no_of_seek = 0;
 
         mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
         try {
@@ -106,8 +107,13 @@
                 mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
                 mp.prepare();
                 mp.start();
+                random_no_of_seek = generator.nextInt(10);
+                // make sure the seek at least run once.
+                if (random_no_of_seek == 0) {
+                    random_no_of_seek = 1;
+                }
                 // Random seek and play
-                for (int j = 0; j < generator.nextInt(10); j++) {
+                for (int j = 0; j < random_no_of_seek; j++) {
                     random_play_time =
                         generator.nextInt(video_duration / 2);
                     Log.v(TAG, "Play time = " + random_play_time);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 13db005..8cfd956 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -50,6 +50,7 @@
  * List of settings that are backed up are stored in the Settings.java file
  */
 public class SettingsBackupAgent extends BackupHelperAgent {
+    private static final boolean DEBUG = true;
 
     private static final String KEY_SYSTEM = "system";
     private static final String KEY_SECURE = "secure";
@@ -242,6 +243,13 @@
             pos += length;
             if (!TextUtils.isEmpty(settingName) && !TextUtils.isEmpty(settingValue)) {
                 //Log.i(TAG, "Restore " + settingName + " = " + settingValue);
+
+                // TODO: versioning rather than just an ad hoc blacklist to handle
+                // older varieties of backed-up data
+                if (invalidSavedSetting(contentUri, settingName, settingValue)) {
+                    continue;
+                }
+
                 if (mSettingsHelper.restoreValue(settingName, settingValue)) {
                     cv.clear();
                     cv.put(Settings.NameValueTable.NAME, settingName);
@@ -252,6 +260,22 @@
         }
     }
 
+    private boolean invalidSavedSetting(Uri contentUri, String settingName, String settingValue) {
+        // Even if these settings were stored, don't use them on restore
+        if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
+            if (settingName.equals(Settings.Secure.PREFERRED_NETWORK_MODE)
+                    || settingName.equals(Settings.Secure.PREFERRED_TTY_MODE)
+                    || settingName.equals(Settings.Secure.CDMA_CELL_BROADCAST_SMS)
+                    || settingName.equals(Settings.Secure.PREFERRED_CDMA_SUBSCRIPTION)
+                    || settingName.equals(Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED)) {
+                if (DEBUG) Log.v(TAG, "Ignoring restore datum: " + settingName);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     private String[] copyAndSort(String[] keys) {
         String[] sortedKeys = new String[keys.length];
         System.arraycopy(keys, 0, sortedKeys, 0, keys.length);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a744486..8914aceac 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -159,7 +159,8 @@
 
     /**
      * Returns the software version number for the device, for example,
-     * the IMEI/SV for GSM phones.
+     * the IMEI/SV for GSM phones. Return null if the software version is
+     * not available.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -168,13 +169,15 @@
         try {
             return getSubscriberInfo().getDeviceSvn();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            return null;
         }
-        return null;
     }
 
     /**
-     * Returns the unique device ID, for example, the IMEI for GSM and the MEID for CDMA
-     * phones.
+     * Returns the unique device ID, for example, the IMEI for GSM and the MEID
+     * for CDMA phones. Return null if device ID is not available.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -183,12 +186,15 @@
         try {
             return getSubscriberInfo().getDeviceId();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            return null;
         }
-        return null;
     }
 
     /**
      * Returns the current location of the device.
+     * Return null if current location is not available.
      *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
@@ -199,8 +205,10 @@
             Bundle bundle = getITelephony().getCellLocation();
             return CellLocation.newFromBundle(bundle);
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            return null;
         }
-        return null;
     }
 
     /**
@@ -216,6 +224,7 @@
         try {
             getITelephony().enableLocationUpdates();
         } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
         }
     }
 
@@ -232,6 +241,7 @@
         try {
             getITelephony().disableLocationUpdates();
         } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
         }
     }
 
@@ -247,9 +257,10 @@
        try {
            return getITelephony().getNeighboringCellInfo();
        } catch (RemoteException ex) {
+           return null;
+       } catch (NullPointerException ex) {
+           return null;
        }
-       return null;
-
     }
 
     /**
@@ -289,7 +300,11 @@
                 // This can happen when the ITelephony interface is not up yet.
                 return getPhoneTypeFromProperty();
             }
-        } catch(RemoteException ex){
+        } catch (RemoteException ex) {
+            // This shouldn't happen in the normal case, as a backup we
+            // read from the system property.
+            return getPhoneTypeFromProperty();
+        } catch (NullPointerException ex) {
             // This shouldn't happen in the normal case, as a backup we
             // read from the system property.
             return getPhoneTypeFromProperty();
@@ -418,9 +433,12 @@
                 // This can happen when the ITelephony interface is not up yet.
                 return NETWORK_TYPE_UNKNOWN;
             }
-        } catch(RemoteException ex){
+        } catch(RemoteException ex) {
             // This shouldn't happen in the normal case
             return NETWORK_TYPE_UNKNOWN;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return NETWORK_TYPE_UNKNOWN;
         }
     }
 
@@ -489,6 +507,9 @@
         } catch (RemoteException ex) {
             // Assume no ICC card if remote exception which shouldn't happen
             return false;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return false;
         }
     }
 
@@ -556,7 +577,8 @@
     }
 
     /**
-     * Returns the serial number of the SIM, if applicable.
+     * Returns the serial number of the SIM, if applicable. Return null if it is
+     * unavailable.
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -565,8 +587,11 @@
         try {
             return getSubscriberInfo().getIccSerialNumber();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
         }
-        return null;
     }
 
     //
@@ -577,6 +602,7 @@
 
     /**
      * Returns the unique subscriber ID, for example, the IMSI for a GSM phone.
+     * Return null if it is unavailable.
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -585,13 +611,16 @@
         try {
             return getSubscriberInfo().getSubscriberId();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
         }
-        return null;
     }
 
     /**
      * Returns the phone number string for line 1, for example, the MSISDN
-     * for a GSM phone.
+     * for a GSM phone. Return null if it is unavailable.
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -600,12 +629,16 @@
         try {
             return getSubscriberInfo().getLine1Number();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
         }
-        return null;
     }
 
     /**
      * Returns the alphabetic identifier associated with the line 1 number.
+     * Return null if it is unavailable.
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -616,12 +649,15 @@
         try {
             return getSubscriberInfo().getLine1AlphaTag();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
         }
-        return null;
     }
 
     /**
-     * Returns the voice mail number.
+     * Returns the voice mail number. Return null if it is unavailable.
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -630,12 +666,15 @@
         try {
             return getSubscriberInfo().getVoiceMailNumber();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
         }
-        return null;
     }
 
     /**
-     * Returns the voice mail count.
+     * Returns the voice mail count. Return 0 if unavailable.
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -645,8 +684,11 @@
         try {
             return getITelephony().getVoiceMessageCount();
         } catch (RemoteException ex) {
+            return 0;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return 0;
         }
-        return 0;
     }
 
     /**
@@ -660,8 +702,11 @@
         try {
             return getSubscriberInfo().getVoiceMailAlphaTag();
         } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
         }
-        return null;
     }
 
     private IPhoneSubInfo getSubscriberInfo() {
@@ -759,6 +804,8 @@
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return DATA_DISCONNECTED;
+        } catch (NullPointerException ex) {
+            return DATA_DISCONNECTED;
         }
     }
 
@@ -802,6 +849,8 @@
             mRegistry.listen(pkgForDebug, listener.callback, events, notifyNow);
         } catch (RemoteException ex) {
             // system process dead
+        } catch (NullPointerException ex) {
+            // system process dead
         }
     }
 
@@ -816,6 +865,8 @@
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return -1;
+        } catch (NullPointerException ex) {
+            return -1;
         }
     }
 
@@ -832,6 +883,8 @@
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return -1;
+        } catch (NullPointerException ex) {
+            return -1;
         }
     }
 
@@ -846,6 +899,8 @@
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return null;
+        } catch (NullPointerException ex) {
+            return null;
         }
     }
 }
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index ece708a..8d2785a 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -100,7 +100,6 @@
     public static final int EVENT_CLEAN_UP_CONNECTION = 34;
     protected static final int EVENT_CDMA_OTA_PROVISION = 35;
     protected static final int EVENT_RESTART_RADIO = 36;
-    private static final int EVENT_ENABLE_APN_REQUEST = 37;
 
     /***** Constants *****/
 
@@ -126,6 +125,10 @@
         + "5000,10000,20000,40000,80000:5000,160000:5000,"
         + "320000:5000,640000:5000,1280000:5000,1800000:5000";
 
+    /** Retry configuration for secondary networks: 4 tries in 20 sec */
+    protected static final String SECONDARY_DATA_RETRY_CONFIG =
+            "max_retries=3; 5000, 5000, 5000";
+
     /** Slow poll when attempting connection recovery. */
     protected static final int POLL_NETSTAT_SLOW_MILLIS = 5000;
     /** Default ping deadline, in seconds. */
@@ -171,7 +174,7 @@
     protected boolean netStatPollEnabled = false;
 
     /** Manage the behavior of data retry after failure */
-    protected final RetryManager mRetryMgr = new RetryManager();
+    protected RetryManager mRetryMgr = new RetryManager();
 
     // wifi connection status will be updated by sticky intent
     protected boolean mIsWifiConnected = false;
@@ -266,33 +269,8 @@
     public void handleMessage (Message msg) {
         switch (msg.what) {
 
-            case EVENT_ENABLE_APN_REQUEST:
-                int apnId = msg.arg1;
-                synchronized (this) {
-                    if (DBG) {
-                        Log.d(LOG_TAG, "got EVENT_ENABLE_APN_REQUEST with apnType = " + apnId +
-                                " and enable = " + msg.arg2);
-                        Log.d(LOG_TAG, "dataEnabled[apnId] = " + dataEnabled[apnId] +
-                                ", enabledCount = " + enabledCount);
-                    }
-                    if (msg.arg2 == APN_ENABLED) {
-                        // enable
-                        if (!dataEnabled[apnId]) {
-                            dataEnabled[apnId] = true;
-                            enabledCount++;
-                        }
-                        onTrySetupData(null);
-                    } else {
-                        // disable
-                        if (dataEnabled[apnId]) {
-                            dataEnabled[apnId] = false;
-                            enabledCount--;
-                            if (enabledCount == 0) {
-                                onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
-                            }
-                        }
-                    }
-                }
+            case EVENT_ENABLE_NEW_APN:
+                onEnableApn(msg.arg1, msg.arg2);
                 break;
 
             case EVENT_TRY_SETUP_DATA:
@@ -392,6 +370,24 @@
         }
     }
 
+    protected String apnIdToType(int id) {
+        switch (id) {
+        case APN_DEFAULT_ID:
+            return Phone.APN_TYPE_DEFAULT;
+        case APN_MMS_ID:
+            return Phone.APN_TYPE_MMS;
+        case APN_SUPL_ID:
+            return Phone.APN_TYPE_SUPL;
+        case APN_DUN_ID:
+            return Phone.APN_TYPE_DUN;
+        case APN_HIPRI_ID:
+            return Phone.APN_TYPE_HIPRI;
+        default:
+            Log.e(LOG_TAG, "Unknown id (" + id + ") in apnIdToType");
+            return Phone.APN_TYPE_DEFAULT;
+        }
+    }
+
     protected abstract boolean isApnTypeActive(String type);
 
     protected abstract boolean isApnTypeAvailable(String type);
@@ -449,8 +445,6 @@
         }
 
         setEnabled(id, true);
-        mRequestedApnType = type;
-        sendMessage(obtainMessage(EVENT_ENABLE_NEW_APN));
         return Phone.APN_REQUEST_STARTED;
     }
 
@@ -471,7 +465,6 @@
         if (isEnabled(id)) {
             setEnabled(id, false);
             if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
-                mRequestedApnType = Phone.APN_TYPE_DEFAULT;
                 if (dataEnabled[APN_DEFAULT_ID]) {
                     return Phone.APN_ALREADY_ACTIVE;
                 } else {
@@ -485,16 +478,56 @@
         }
     }
 
-    protected void setEnabled(int id, boolean enable) {
+    private void setEnabled(int id, boolean enable) {
         if (DBG) Log.d(LOG_TAG, "setEnabled(" + id + ", " + enable + ") with old state = " +
                 dataEnabled[id] + " and enabledCount = " + enabledCount);
 
-        Message msg = obtainMessage(EVENT_ENABLE_APN_REQUEST);
+        Message msg = obtainMessage(EVENT_ENABLE_NEW_APN);
         msg.arg1 = id;
         msg.arg2 = (enable ? APN_ENABLED : APN_DISABLED);
         sendMessage(msg);
     }
 
+    protected synchronized void onEnableApn(int apnId, int enabled) {
+        if (DBG) {
+            Log.d(LOG_TAG, "got EVENT_APN_ENABLE_REQUEST with apnType = " + apnId +
+                    " and enable = " + enabled);
+            Log.d(LOG_TAG, "dataEnabled[apnId] = " + dataEnabled[apnId] +
+                    ", enabledCount = " + enabledCount);
+        }
+        if (enabled == APN_ENABLED) {
+            if (!dataEnabled[apnId]) {
+                mRequestedApnType = apnIdToType(apnId);
+                onEnableNewApn();
+
+                dataEnabled[apnId] = true;
+                enabledCount++;
+            }
+            onTrySetupData(null);
+        } else {
+            // disable
+            if (dataEnabled[apnId]) {
+                dataEnabled[apnId] = false;
+                enabledCount--;
+                if (enabledCount == 0) {
+                    onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
+                } else if (dataEnabled[APN_DEFAULT_ID] == true) {
+                    mRequestedApnType = Phone.APN_TYPE_DEFAULT;
+                    onEnableNewApn();
+                }
+            }
+        }
+    }
+
+    /**
+     * Called when we switch APNs.
+     *
+     * mRequestedApnType is set prior to call
+     * To be overridden.
+     */
+    protected void onEnableNewApn() {
+    }
+
     /**
      * Prevent mobile data connections from being established,
      * or once again allow mobile data connections. If the state
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 5203d3f..f32837f 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -154,6 +154,7 @@
     static final String REASON_CDMA_DATA_DETACHED = "cdmaDataDetached";
     static final String REASON_APN_CHANGED = "apnChanged";
     static final String REASON_APN_SWITCHED = "apnSwitched";
+    static final String REASON_APN_FAILED = "apnFailed";
     static final String REASON_RESTORE_DEFAULT_APN = "restoreDefaultApn";
     static final String REASON_RADIO_TURNED_OFF = "radioTurnedOff";
     static final String REASON_PDP_RESET = "pdpReset";
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 0215ab2..b063e0a 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -150,6 +150,11 @@
     static final String APN_ID = "apn_id";
     private boolean canSetPreferApn = false;
 
+    // for tracking retrys on the default APN
+    private RetryManager mDefaultRetryManager;
+    // for tracking retrys on a secondary APN
+    private RetryManager mSecondaryRetryManager;
+
     BroadcastReceiver mIntentReceiver = new BroadcastReceiver ()
     {
         @Override
@@ -253,6 +258,19 @@
                 mRetryMgr.configure(20, 2000, 1000);
             }
         }
+
+        mDefaultRetryManager = mRetryMgr;
+        mSecondaryRetryManager = new RetryManager();
+
+        if (!mSecondaryRetryManager.configure(SystemProperties.get(
+                "ro.gsm.2nd_data_retry_config"))) {
+            if (!mSecondaryRetryManager.configure(SECONDARY_DATA_RETRY_CONFIG)) {
+                // Should never happen, log an error and default to a simple sequence.
+                Log.e(LOG_TAG, "Could note configure using SECONDARY_DATA_RETRY_CONFIG="
+                        + SECONDARY_DATA_RETRY_CONFIG);
+                mSecondaryRetryManager.configure("max_retries=3, 333, 333, 333");
+            }
+        }
     }
 
     public void dispose() {
@@ -1019,6 +1037,12 @@
     private void reconnectAfterFail(FailCause lastFailCauseCode, String reason) {
         if (state == State.FAILED) {
             if (!mRetryMgr.isRetryNeeded()) {
+                if (!mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) {
+                    // if no more retries on a secondary APN attempt, tell the world and revert.
+                    phone.notifyDataConnection(Phone.REASON_APN_FAILED);
+                    onEnableApn(apnTypeToId(mRequestedApnType), APN_DISABLED);
+                    return;
+                }
                 if (mReregisterOnReconnectFailure) {
                     // We've re-registerd once now just retry forever.
                     mRetryMgr.retryForeverUsingLastTimeout();
@@ -1069,7 +1093,16 @@
         sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED));
     }
 
+    @Override
     protected void onEnableNewApn() {
+        // change our retry manager to use the appropriate numbers for the new APN
+        if (mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) {
+            mRetryMgr = mDefaultRetryManager;
+        } else {
+            mRetryMgr = mSecondaryRetryManager;
+        }
+        mRetryMgr.resetRetryCount();
+
         // TODO:  To support simultaneous PDP contexts, this should really only call
         // cleanUpConnection if it needs to free up a PdpConnection.
         cleanUpConnection(true, Phone.REASON_APN_SWITCHED);
@@ -1189,6 +1222,10 @@
             // No try for permanent failure
             if (cause.isPermanentFail()) {
                 notifyNoData(cause);
+                if (!mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) {
+                    phone.notifyDataConnection(Phone.REASON_APN_FAILED);
+                    onEnableApn(apnTypeToId(mRequestedApnType), APN_DISABLED);
+                }
                 return;
             }
 
@@ -1381,9 +1418,7 @@
 
     private void startDelayedRetry(PdpConnection.FailCause cause, String reason) {
         notifyNoData(cause);
-        if (mRequestedApnType == Phone.APN_TYPE_DEFAULT) {
-            reconnectAfterFail(cause, reason);
-        }
+        reconnectAfterFail(cause, reason);
     }
 
     private void setPreferredApn(int pos) {
@@ -1442,10 +1477,6 @@
                 onRecordsLoaded();
                 break;
 
-            case EVENT_ENABLE_NEW_APN:
-                onEnableNewApn();
-                break;
-
             case EVENT_GPRS_DETACHED:
                 onGprsDetached();
                 break;
diff --git a/test-runner/android/test/AndroidTestRunner.java b/test-runner/android/test/AndroidTestRunner.java
index 358b7e9..0f1599a 100644
--- a/test-runner/android/test/AndroidTestRunner.java
+++ b/test-runner/android/test/AndroidTestRunner.java
@@ -18,6 +18,8 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.PerformanceCollector.PerformanceResultsWriter;
+
 import com.google.android.collect.Lists;
 import junit.framework.Test;
 import junit.framework.TestCase;
@@ -39,6 +41,7 @@
 
     private List<TestListener> mTestListeners = Lists.newArrayList();
     private Instrumentation mInstrumentation;
+    private PerformanceResultsWriter mPerfWriter;
 
     @SuppressWarnings("unchecked")
     public void setTestClassName(String testClassName, String testMethodName) {
@@ -162,6 +165,7 @@
         for (TestCase testCase : mTestCases) {
             setContextIfAndroidTestCase(testCase, mContext, testContext);
             setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation);
+            setPerformanceWriterIfPerformanceTestCase(testCase, mPerfWriter);
             testCase.run(mTestResult);
         }
     }
@@ -184,6 +188,13 @@
         }
     }
 
+    private void setPerformanceWriterIfPerformanceTestCase(
+            Test test, PerformanceResultsWriter writer) {
+        if (PerformanceTestBase.class.isAssignableFrom(test.getClass())) {
+            ((PerformanceTestBase) test).setPerformanceResultsWriter(writer);
+        }
+    }
+
     public void setInstrumentation(Instrumentation instrumentation) {
         mInstrumentation = instrumentation;
     }
@@ -197,6 +208,13 @@
         setInstrumentation(instrumentation);
     }
 
+    /**
+     * {@hide} Pending approval for public API.
+     */
+    public void setPerformanceResultsWriter(PerformanceResultsWriter writer) {
+        mPerfWriter = writer;
+    }
+
     @Override
     protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
         return mContext.getClassLoader().loadClass(suiteClassName);
diff --git a/test-runner/android/test/InstrumentationTestRunner.java b/test-runner/android/test/InstrumentationTestRunner.java
index 23f0ed4..b9978d6 100644
--- a/test-runner/android/test/InstrumentationTestRunner.java
+++ b/test-runner/android/test/InstrumentationTestRunner.java
@@ -17,17 +17,31 @@
 package android.test;
 
 import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE;
+
+import com.android.internal.util.Predicate;
+
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Looper;
+import android.os.Parcelable;
+import android.os.PerformanceCollector;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.PerformanceCollector.PerformanceResultsWriter;
 import android.test.suitebuilder.TestMethod;
 import android.test.suitebuilder.TestPredicates;
 import android.test.suitebuilder.TestSuiteBuilder;
 import android.util.Log;
 
-import com.android.internal.util.Predicate;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
@@ -38,22 +52,13 @@
 import junit.runner.BaseTestRunner;
 import junit.textui.ResultPrinter;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.PrintStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-
 /**
  * An {@link Instrumentation} that runs various types of {@link junit.framework.TestCase}s against
  * an Android package (application). Typical usage:
  * <ol>
  * <li>Write {@link junit.framework.TestCase}s that perform unit, functional, or performance tests
  * against the classes in your package.  Typically these are subclassed from:
- *   <ul><li>{@link android.test.ActivityInstrumentationTestCase}</li>
+ *   <ul><li>{@link android.test.ActivityInstrumentationTestCase2}</li>
  *   <li>{@link android.test.ActivityUnitTestCase}</li>
  *   <li>{@link android.test.AndroidTestCase}</li>
  *   <li>{@link android.test.ApplicationTestCase}</li>
@@ -111,13 +116,13 @@
  * <p/>
  * <b>To run in 'log only' mode</b>
  * -e log true
- * This option will load and iterate through all test classes and methods, but will bypass actual 
- * test execution. Useful for quickly obtaining info on the tests to be executed by an 
+ * This option will load and iterate through all test classes and methods, but will bypass actual
+ * test execution. Useful for quickly obtaining info on the tests to be executed by an
  * instrumentation command.
  * <p/>
  * <b>To generate EMMA code coverage:</b>
  * -e coverage true
- * Note: this requires an emma instrumented build. By default, the code coverage results file 
+ * Note: this requires an emma instrumented build. By default, the code coverage results file
  * will be saved in a /data/<app>/coverage.ec file, unless overridden by coverageFile flag (see
  * below)
  * <p/>
@@ -129,11 +134,10 @@
 
 /* (not JavaDoc)
  * Although not necessary in most case, another way to use this class is to extend it and have the
- * derived class return
- * the desired test suite from the {@link #getTestSuite()} method. The test suite returned from this
- * method will be used if no target class is defined in the meta-data or command line argument
- * parameters. If a derived class is used it needs to be added as an instrumentation to the
- * AndroidManifest.xml and the command to run it would look like:
+ * derived class return the desired test suite from the {@link #getTestSuite()} method. The test
+ * suite returned from this method will be used if no target class is defined in the meta-data or
+ * command line argument parameters. If a derived class is used it needs to be added as an
+ * instrumentation to the AndroidManifest.xml and the command to run it would look like:
  * <p/>
  * adb shell am instrument -w com.android.foo/<i>com.android.FooInstrumentationTestRunner</i>
  * <p/>
@@ -155,66 +159,65 @@
     public static final String ARGUMENT_DELAY_MSEC = "delay_msec";
 
     private static final String SMALL_SUITE = "small";
-    private static final String MEDIUM_SUITE = "medium";  
+    private static final String MEDIUM_SUITE = "medium";
     private static final String LARGE_SUITE = "large";
-    
+
     private static final String ARGUMENT_LOG_ONLY = "log";
 
-   
     /**
-     * This constant defines the maximum allowed runtime (in ms) for a test included in the "small" suite. 
-     * It is used to make an educated guess at what suite an unlabeled test belongs.
+     * This constant defines the maximum allowed runtime (in ms) for a test included in the "small"
+     * suite. It is used to make an educated guess at what suite an unlabeled test belongs.
      */
     private static final float SMALL_SUITE_MAX_RUNTIME = 100;
-    
+
     /**
-     * This constant defines the maximum allowed runtime (in ms) for a test included in the "medium" suite. 
-     * It is used to make an educated guess at what suite an unlabeled test belongs.
+     * This constant defines the maximum allowed runtime (in ms) for a test included in the
+     * "medium" suite. It is used to make an educated guess at what suite an unlabeled test belongs.
      */
     private static final float MEDIUM_SUITE_MAX_RUNTIME = 1000;
-    
+
     /**
-     * The following keys are used in the status bundle to provide structured reports to 
-     * an IInstrumentationWatcher. 
+     * The following keys are used in the status bundle to provide structured reports to
+     * an IInstrumentationWatcher.
      */
 
     /**
-     * This value, if stored with key {@link android.app.Instrumentation#REPORT_KEY_IDENTIFIER}, 
+     * This value, if stored with key {@link android.app.Instrumentation#REPORT_KEY_IDENTIFIER},
      * identifies InstrumentationTestRunner as the source of the report.  This is sent with all
      * status messages.
      */
     public static final String REPORT_VALUE_ID = "InstrumentationTestRunner";
     /**
-     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * identifies the total number of tests that are being run.  This is sent with all status
      * messages.
      */
     public static final String REPORT_KEY_NUM_TOTAL = "numtests";
     /**
-     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * identifies the sequence number of the current test.  This is sent with any status message
      * describing a specific test being started or completed.
      */
     public static final String REPORT_KEY_NUM_CURRENT = "current";
     /**
-     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * identifies the name of the current test class.  This is sent with any status message
      * describing a specific test being started or completed.
      */
     public static final String REPORT_KEY_NAME_CLASS = "class";
     /**
-     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * identifies the name of the current test.  This is sent with any status message
      * describing a specific test being started or completed.
      */
     public static final String REPORT_KEY_NAME_TEST = "test";
     /**
-     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * reports the run time in seconds of the current test.
      */
     private static final String REPORT_KEY_RUN_TIME = "runtime";
     /**
-     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * reports the guessed suite assignment for the current test.
      */
     private static final String REPORT_KEY_SUITE_ASSIGNMENT = "suiteassignment";
@@ -224,6 +227,19 @@
      */
     private static final String REPORT_KEY_COVERAGE_PATH = "coverageFilePath";
     /**
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
+     * reports the cpu time in milliseconds of the current test.
+     */
+    private static final String REPORT_KEY_PERF_CPU_TIME =
+        "performance." + PerformanceCollector.METRIC_KEY_CPU_TIME;
+    /**
+     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
+     * reports the run time in milliseconds of the current test.
+     */
+    private static final String REPORT_KEY_PERF_EXECUTION_TIME =
+        "performance." + PerformanceCollector.METRIC_KEY_EXECUTION_TIME;
+
+    /**
      * The test is starting.
      */
     public static final int REPORT_VALUE_RESULT_START = 1;
@@ -240,15 +256,15 @@
      */
     public static final int REPORT_VALUE_RESULT_FAILURE = -2;
     /**
-     * If included in the status bundle sent to an IInstrumentationWatcher, this key 
-     * identifies a stack trace describing an error or failure.  This is sent with any status 
+     * If included in the status bundle sent to an IInstrumentationWatcher, this key
+     * identifies a stack trace describing an error or failure.  This is sent with any status
      * message describing a specific test being completed.
      */
     public static final String REPORT_KEY_STACK = "stack";
 
     // Default file name for code coverage
     private static final String DEFAULT_COVERAGE_FILE_NAME = "coverage.ec";
-    
+
     private static final String LOG_TAG = "InstrumentationTestRunner";
 
     private final Bundle mResults = new Bundle();
@@ -316,7 +332,7 @@
                 if (testSuite != null) {
                     testSuiteBuilder.addTestSuite(testSuite);
                 } else {
-                    // no package or class bundle arguments were supplied, and no test suite 
+                    // no package or class bundle arguments were supplied, and no test suite
                     // provided so add all tests in application
                     testSuiteBuilder.includePackages("");
                 }
@@ -324,7 +340,7 @@
         } else {
             parseTestClasses(testClassesArg, testSuiteBuilder);
         }
-        
+
         testSuiteBuilder.addRequirements(getBuilderRequirements());
 
         mTestRunner = getAndroidTestRunner();
@@ -336,8 +352,10 @@
         if (mSuiteAssignmentMode) {
             mTestRunner.addTestListener(new SuiteAssignmentPrinter());
         } else {
+            WatcherResultPrinter resultPrinter = new WatcherResultPrinter(mTestCount);
             mTestRunner.addTestListener(new TestPrinter("TestRunner", false));
-            mTestRunner.addTestListener(new WatcherResultPrinter(mTestCount));
+            mTestRunner.addTestListener(resultPrinter);
+            mTestRunner.setPerformanceResultsWriter(resultPrinter);
         }
         start();
     }
@@ -347,7 +365,8 @@
     }
 
     /**
-     * Parses and loads the specified set of test classes 
+     * Parses and loads the specified set of test classes
+     *
      * @param testClassArg - comma-separated list of test classes and methods
      * @param testSuiteBuilder - builder to add tests to
      */
@@ -360,8 +379,9 @@
 
     /**
      * Parse and load the given test class and, optionally, method
-     * @param testClassName - full package name of test class and optionally method to add. Expected
-     *   format: com.android.TestClass#testMethod
+     *
+     * @param testClassName - full package name of test class and optionally method to add.
+     *        Expected format: com.android.TestClass#testMethod
      * @param testSuiteBuilder - builder to add tests to
      */
     private void parseTestClass(String testClassName, TestSuiteBuilder testSuiteBuilder) {
@@ -372,8 +392,7 @@
             testMethodName = testClassName.substring(methodSeparatorIndex + 1);
             testClassName = testClassName.substring(0, methodSeparatorIndex);
         }
-        testSuiteBuilder.addTestClassByName(testClassName, testMethodName, 
-                getTargetContext());
+        testSuiteBuilder.addTestClassByName(testClassName, testMethodName, getTargetContext());
     }
 
     protected AndroidTestRunner getAndroidTestRunner() {
@@ -384,12 +403,12 @@
         String tagString = arguments.getString(tag);
         return tagString != null && Boolean.parseBoolean(tagString);
     }
-    
+
     /*
      * Returns the size predicate object, corresponding to the "size" argument value.
      */
     private Predicate<TestMethod> getSizePredicateFromArg(String sizeArg) {
-     
+
         if (SMALL_SUITE.equals(sizeArg)) {
             return TestPredicates.SELECT_SMALL;
         } else if (MEDIUM_SUITE.equals(sizeArg)) {
@@ -400,11 +419,11 @@
             return null;
         }
     }
-  
+
     @Override
     public void onStart() {
         Looper.prepare();
-        
+
         if (mJustCount) {
             mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
             mResults.putInt(REPORT_KEY_NUM_TOTAL, mTestCount);
@@ -413,30 +432,30 @@
             if (mDebug) {
                 Debug.waitForDebugger();
             }
-    
+
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             PrintStream writer = new PrintStream(byteArrayOutputStream);
             try {
                 StringResultPrinter resultPrinter = new StringResultPrinter(writer);
-    
+
                 mTestRunner.addTestListener(resultPrinter);
-                
+
                 long startTime = System.currentTimeMillis();
                 mTestRunner.runTest();
                 long runTime = System.currentTimeMillis() - startTime;
-    
+
                 resultPrinter.print(mTestRunner.getTestResult(), runTime);
             } finally {
-                mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 
-                        String.format("\nTest results for %s=%s", 
-                        mTestRunner.getTestClassName(), 
+                mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+                        String.format("\nTest results for %s=%s",
+                        mTestRunner.getTestClassName(),
                         byteArrayOutputStream.toString()));
 
                 if (mCoverage) {
                     generateCoverageReport();
                 }
                 writer.close();
-                
+
                 finish(Activity.RESULT_OK, mResults);
             }
         }
@@ -459,7 +478,7 @@
     public ClassLoader getLoader() {
         return null;
     }
-    
+
     private void generateCoverageReport() {
         // use reflection to call emma dump coverage method, to avoid
         // always statically compiling against emma jar
@@ -467,9 +486,9 @@
         java.io.File coverageFile = new java.io.File(coverageFilePath);
         try {
             Class emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
-            Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData", 
+            Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
                     coverageFile.getClass(), boolean.class, boolean.class);
-            
+
             dumpCoverageMethod.invoke(null, coverageFile, false, false);
             // output path to generated coverage file so it can be parsed by a test harness if
             // needed
@@ -495,15 +514,14 @@
     private String getCoverageFilePath() {
         if (mCoverageFilePath == null) {
             return getTargetContext().getFilesDir().getAbsolutePath() + File.separator +
-                    DEFAULT_COVERAGE_FILE_NAME;
-         }
-        else {
+                   DEFAULT_COVERAGE_FILE_NAME;
+        } else {
             return mCoverageFilePath;
         }
     }
 
     private void reportEmmaError(Exception e) {
-        reportEmmaError("", e); 
+        reportEmmaError("", e);
     }
 
     private void reportEmmaError(String hint, Exception e) {
@@ -524,30 +542,29 @@
             printFooter(result);
         }
     }
-    
+
     /**
-     * This class sends status reports back to the IInstrumentationWatcher about 
+     * This class sends status reports back to the IInstrumentationWatcher about
      * which suite each test belongs.
      */
-    private class SuiteAssignmentPrinter implements TestListener
-    {
-        
+    private class SuiteAssignmentPrinter implements TestListener {
+
         private Bundle mTestResult;
         private long mStartTime;
         private long mEndTime;
         private boolean mTimingValid;
-        
+
         public SuiteAssignmentPrinter() {
         }
-        
+
         /**
          * send a status for the start of a each test, so long tests can be seen as "running"
          */
         public void startTest(Test test) {
             mTimingValid = true;
-            mStartTime = System.currentTimeMillis(); 
+            mStartTime = System.currentTimeMillis();
         }
-        
+
         /**
          * @see junit.framework.TestListener#addError(Test, Throwable)
          */
@@ -576,7 +593,7 @@
                 runTime = -1;
             } else {
                 runTime = mEndTime - mStartTime;
-                if (runTime < SMALL_SUITE_MAX_RUNTIME 
+                if (runTime < SMALL_SUITE_MAX_RUNTIME
                         && !InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
                     assignmentSuite = SMALL_SUITE;
                 } else if (runTime < MEDIUM_SUITE_MAX_RUNTIME) {
@@ -588,8 +605,8 @@
             // Clear mStartTime so that we can verify that it gets set next time.
             mStartTime = -1;
 
-            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 
-                    test.getClass().getName() + "#" + ((TestCase) test).getName() 
+            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+                    test.getClass().getName() + "#" + ((TestCase) test).getName()
                     + "\nin " + assignmentSuite + " suite\nrunTime: "
                     + String.valueOf(runTime) + "\n");
             mTestResult.putFloat(REPORT_KEY_RUN_TIME, runTime);
@@ -598,36 +615,40 @@
             sendStatus(0, mTestResult);
         }
     }
-    
+
     /**
      * This class sends status reports back to the IInstrumentationWatcher
      */
-    private class WatcherResultPrinter implements TestListener
-    {
+    private class WatcherResultPrinter implements TestListener, PerformanceResultsWriter {
         private final Bundle mResultTemplate;
         Bundle mTestResult;
         int mTestNum = 0;
         int mTestResultCode = 0;
         String mTestClass = null;
-        
+        boolean mIsTimedTest = false;
+        long mCpuTime = 0;
+        long mExecTime = 0;
+
         public WatcherResultPrinter(int numTests) {
             mResultTemplate = new Bundle();
             mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
             mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
         }
-        
+
         /**
-         * send a status for the start of a each test, so long tests can be seen as "running"
+         * send a status for the start of a each test, so long tests can be seen
+         * as "running"
          */
         public void startTest(Test test) {
             String testClass = test.getClass().getName();
+            String testName = ((TestCase)test).getName();
             mTestResult = new Bundle(mResultTemplate);
             mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass);
-            mTestResult.putString(REPORT_KEY_NAME_TEST, ((TestCase) test).getName());
+            mTestResult.putString(REPORT_KEY_NAME_TEST, testName);
             mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum);
             // pretty printing
             if (testClass != null && !testClass.equals(mTestClass)) {
-                mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 
+                mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
                         String.format("\n%s:", testClass));
                 mTestClass = testClass;
             } else {
@@ -635,9 +656,9 @@
             }
 
             // The delay_msec parameter is normally used to provide buffers of idle time
-            // for power measurement purposes.  To make sure there is a delay before and after
+            // for power measurement purposes. To make sure there is a delay before and after
             // every test in a suite, we delay *after* every test (see endTest below) and also
-            // delay *before* the first test.  So, delay test1 delay test2 delay.
+            // delay *before* the first test. So, delay test1 delay test2 delay.
 
             try {
                 if (mTestNum == 1) Thread.sleep(mDelayMsec);
@@ -647,8 +668,25 @@
 
             sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
             mTestResultCode = 0;
+
+            mIsTimedTest = false;
+            try {
+                // Look for TimedTest annotation on both test class and test
+                // method
+                mIsTimedTest = test.getClass().isAnnotationPresent(TimedTest.class) ||
+                    test.getClass().getMethod(testName).isAnnotationPresent(TimedTest.class);
+            } catch (SecurityException e) {
+                throw new IllegalStateException(e);
+            } catch (NoSuchMethodException e) {
+                throw new IllegalStateException(e);
+            }
+
+            if (mIsTimedTest) {
+                mExecTime = SystemClock.uptimeMillis();
+                mCpuTime = Process.getElapsedCpuTime();
+            }
         }
-        
+
         /**
          * @see junit.framework.TestListener#addError(Test, Throwable)
          */
@@ -656,9 +694,9 @@
             mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
             mTestResultCode = REPORT_VALUE_RESULT_ERROR;
             // pretty printing
-            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 
-                    String.format("\nError in %s:\n%s", 
-                            ((TestCase) test).getName(), BaseTestRunner.getFilteredTrace(t)));
+            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+                String.format("\nError in %s:\n%s",
+                    ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
         }
 
         /**
@@ -668,28 +706,68 @@
             mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
             mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
             // pretty printing
-            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 
-                    String.format("\nFailure in %s:\n%s", 
-                            ((TestCase) test).getName(), BaseTestRunner.getFilteredTrace(t)));
+            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+                String.format("\nFailure in %s:\n%s",
+                    ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
         }
 
         /**
          * @see junit.framework.TestListener#endTest(Test)
          */
         public void endTest(Test test) {
+            if (mIsTimedTest) {
+                mCpuTime = Process.getElapsedCpuTime() - mCpuTime;
+                mExecTime = SystemClock.uptimeMillis() - mExecTime;
+                mTestResult.putLong(REPORT_KEY_PERF_CPU_TIME, mCpuTime);
+                mTestResult.putLong(REPORT_KEY_PERF_EXECUTION_TIME, mExecTime);
+            }
+
             if (mTestResultCode == 0) {
                 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
             }
             sendStatus(mTestResultCode, mTestResult);
 
-            try {  // Sleep after every test, if specified
+            try { // Sleep after every test, if specified
                 Thread.sleep(mDelayMsec);
             } catch (InterruptedException e) {
                 throw new IllegalStateException(e);
             }
         }
 
+        public void writeBeginSnapshot(String label) {
+            // Do nothing
+        }
+
+        public void writeEndSnapshot(Bundle results) {
+            // Copy all snapshot data fields as type long into mResults, which
+            // is outputted via Instrumentation.finish
+            for (String key : results.keySet()) {
+                mResults.putLong(key, results.getLong(key));
+            }
+        }
+
+        public void writeStartTiming(String label) {
+            // Do nothing
+        }
+
+        public void writeStopTiming(Bundle results) {
+            // Copy results into mTestResult by flattening list of iterations,
+            // which is outputted via WatcherResultPrinter.endTest
+            int i = 0;
+            for (Parcelable p :
+                    results.getParcelableArrayList(PerformanceCollector.METRIC_KEY_ITERATIONS)) {
+                Bundle iteration = (Bundle)p;
+                String index = "performance.iteration" + i + ".";
+                mTestResult.putString(index + PerformanceCollector.METRIC_KEY_LABEL,
+                        iteration.getString(PerformanceCollector.METRIC_KEY_LABEL));
+                mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_CPU_TIME,
+                        iteration.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME));
+                mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_EXECUTION_TIME,
+                        iteration.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME));
+                i++;
+            }
+        }
+
         // TODO report the end of the cycle
-        // TODO report runtime for each test
     }
 }
diff --git a/test-runner/android/test/PerformanceTestBase.java b/test-runner/android/test/PerformanceTestBase.java
index 93ac90c..572a9b8 100644
--- a/test-runner/android/test/PerformanceTestBase.java
+++ b/test-runner/android/test/PerformanceTestBase.java
@@ -16,13 +16,95 @@
 
 package android.test;
 
-import android.test.PerformanceTestCase;
-import junit.framework.TestCase;
+import android.os.Bundle;
+import android.os.PerformanceCollector;
+import android.os.PerformanceCollector.PerformanceResultsWriter;
+
+import java.lang.reflect.Method;
 
 /**
- * {@hide} Not needed for SDK.
+ * Provides hooks and wrappers to automatically and manually collect and report
+ * performance data in tests.
+ *
+ * {@hide} Pending approval for public API.
  */
-public abstract class PerformanceTestBase extends TestCase implements PerformanceTestCase {
+public class PerformanceTestBase extends InstrumentationTestCase implements PerformanceTestCase {
+
+    private static PerformanceCollector sPerfCollector = new PerformanceCollector();
+    private static int sNumTestMethods = 0;
+    private static int sNumTestMethodsLeft = 0;
+
+    // Count number of tests, used to emulate beforeClass and afterClass from JUnit4
+    public PerformanceTestBase() {
+        if (sNumTestMethods == 0) {
+            Method methods[] = getClass().getMethods();
+            for (Method m : methods) {
+                if (m.getName().startsWith("test")) {
+                    sNumTestMethods ++;
+                    sNumTestMethodsLeft ++;
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // @beforeClass
+        // Will skew timing measured by TestRunner, but not by PerformanceCollector
+        if (sNumTestMethodsLeft == sNumTestMethods) {
+            sPerfCollector.beginSnapshot(this.getClass().getName());
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // @afterClass
+        // Will skew timing measured by TestRunner, but not by PerformanceCollector
+        if (--sNumTestMethodsLeft == 0) {
+            sPerfCollector.endSnapshot();
+        }
+        super.tearDown();
+    }
+
+    public void setPerformanceResultsWriter(PerformanceResultsWriter writer) {
+        sPerfCollector.setPerformanceResultsWriter(writer);
+    }
+
+    /**
+     * @see PerformanceCollector#beginSnapshot(String)
+     */
+    protected void beginSnapshot(String label) {
+        sPerfCollector.beginSnapshot(label);
+    }
+
+    /**
+     * @see PerformanceCollector#endSnapshot()
+     */
+    protected Bundle endSnapshot() {
+        return sPerfCollector.endSnapshot();
+    }
+
+    /**
+     * @see PerformanceCollector#startTiming(String)
+     */
+    protected void startTiming(String label) {
+        sPerfCollector.startTiming(label);
+    }
+
+    /**
+     * @see PerformanceCollector#addIteration(String)
+     */
+    protected Bundle addIteration(String label) {
+        return sPerfCollector.addIteration(label);
+    }
+
+    /**
+     * @see PerformanceCollector#stopTiming(String)
+     */
+    protected Bundle stopTiming(String label) {
+        return sPerfCollector.stopTiming(label);
+    }
 
     public int startPerformance(PerformanceTestCase.Intermediates intermediates) {
         return 0;
@@ -31,12 +113,4 @@
     public boolean isPerformanceOnly() {
         return true;
     }
-
-    /*
-     * Temporary hack to get some things working again.
-     */
-    public void testRun() {
-        throw new RuntimeException("test implementation not provided");
-    }
 }
-
diff --git a/tests/AndroidTests/src/com/android/unit_tests/os/PerformanceCollectorTest.java b/tests/AndroidTests/src/com/android/unit_tests/os/PerformanceCollectorTest.java
new file mode 100644
index 0000000..1a0c2d1
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/os/PerformanceCollectorTest.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.unit_tests.os;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.PerformanceCollector;
+import android.os.PerformanceCollector.PerformanceResultsWriter;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class PerformanceCollectorTest extends TestCase {
+
+    private PerformanceCollector mPerfCollector;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPerfCollector = new PerformanceCollector();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mPerfCollector = null;
+    }
+
+    public void testBeginSnapshotNoWriter() throws Exception {
+        mPerfCollector.beginSnapshot("testBeginSnapshotNoWriter");
+
+        assertTrue((Long)readPrivateField("mSnapshotCpuTime", mPerfCollector) > 0);
+        assertTrue((Long)readPrivateField("mSnapshotExecTime", mPerfCollector) > 0);
+        Bundle snapshot = (Bundle)readPrivateField("mPerfSnapshot", mPerfCollector);
+        assertNotNull(snapshot);
+        assertEquals(2, snapshot.size());
+    }
+
+    @LargeTest
+    public void testEndSnapshotNoWriter() throws Exception {
+        mPerfCollector.beginSnapshot("testEndSnapshotNoWriter");
+        sleepForRandomLongPeriod();
+        Bundle snapshot = mPerfCollector.endSnapshot();
+
+        verifySnapshotBundle(snapshot);
+    }
+
+    public void testStartTimingNoWriter() throws Exception {
+        mPerfCollector.startTiming("testStartTimingNoWriter");
+
+        assertTrue((Long)readPrivateField("mCpuTime", mPerfCollector) > 0);
+        assertTrue((Long)readPrivateField("mExecTime", mPerfCollector) > 0);
+        Bundle measurement = (Bundle)readPrivateField("mPerfMeasurement", mPerfCollector);
+        assertNotNull(measurement);
+        verifyTimingBundle(measurement, new ArrayList<String>());
+    }
+
+    public void testAddIterationNoWriter() throws Exception {
+        mPerfCollector.startTiming("testAddIterationNoWriter");
+        sleepForRandomTinyPeriod();
+        Bundle iteration = mPerfCollector.addIteration("timing1");
+
+        verifyIterationBundle(iteration, "timing1");
+    }
+
+    public void testStopTimingNoWriter() throws Exception {
+        mPerfCollector.startTiming("testStopTimingNoWriter");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("timing2");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("timing3");
+        sleepForRandomShortPeriod();
+        Bundle timing = mPerfCollector.stopTiming("timing4");
+
+        ArrayList<String> labels = new ArrayList<String>();
+        labels.add("timing2");
+        labels.add("timing3");
+        labels.add("timing4");
+        verifyTimingBundle(timing, labels);
+    }
+
+    public void testBeginSnapshot() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.beginSnapshot("testBeginSnapshot");
+
+        assertEquals("testBeginSnapshot", writer.snapshotLabel);
+        assertTrue((Long)readPrivateField("mSnapshotCpuTime", mPerfCollector) > 0);
+        assertTrue((Long)readPrivateField("mSnapshotExecTime", mPerfCollector) > 0);
+        Bundle snapshot = (Bundle)readPrivateField("mPerfSnapshot", mPerfCollector);
+        assertNotNull(snapshot);
+        assertEquals(2, snapshot.size());
+    }
+
+    @LargeTest
+    public void testEndSnapshot() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.beginSnapshot("testEndSnapshot");
+        sleepForRandomLongPeriod();
+        Bundle snapshot1 = mPerfCollector.endSnapshot();
+        Bundle snapshot2 = writer.snapshotResults;
+
+        assertTrue(snapshot1.equals(snapshot2));
+        verifySnapshotBundle(snapshot1);
+    }
+
+    public void testStartTiming() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.startTiming("testStartTiming");
+
+        assertEquals("testStartTiming", writer.timingLabel);
+        assertTrue((Long)readPrivateField("mCpuTime", mPerfCollector) > 0);
+        assertTrue((Long)readPrivateField("mExecTime", mPerfCollector) > 0);
+        Bundle measurement = (Bundle)readPrivateField("mPerfMeasurement", mPerfCollector);
+        assertNotNull(measurement);
+        verifyTimingBundle(measurement, new ArrayList<String>());
+    }
+
+    public void testAddIteration() throws Exception {
+        mPerfCollector.startTiming("testAddIteration");
+        sleepForRandomTinyPeriod();
+        Bundle iteration = mPerfCollector.addIteration("timing5");
+
+        verifyIterationBundle(iteration, "timing5");
+    }
+
+    public void testStopTiming() throws Exception {
+        mPerfCollector.startTiming("testStopTiming");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("timing6");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("timing7");
+        sleepForRandomShortPeriod();
+        Bundle timing = mPerfCollector.stopTiming("timing8");
+
+        ArrayList<String> labels = new ArrayList<String>();
+        labels.add("timing6");
+        labels.add("timing7");
+        labels.add("timing8");
+        verifyTimingBundle(timing, labels);
+    }
+
+    @LargeTest
+    public void testSimpleSequence() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.beginSnapshot("testSimpleSequence");
+        mPerfCollector.startTiming("testSimpleSequenceTiming");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration1");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration2");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration3");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration4");
+        sleepForRandomShortPeriod();
+        Bundle timing = mPerfCollector.stopTiming("iteration5");
+        sleepForRandomLongPeriod();
+        Bundle snapshot1 = mPerfCollector.endSnapshot();
+        Bundle snapshot2 = writer.snapshotResults;
+
+        assertTrue(snapshot1.equals(snapshot2));
+        verifySnapshotBundle(snapshot1);
+
+        ArrayList<String> labels = new ArrayList<String>();
+        labels.add("iteration1");
+        labels.add("iteration2");
+        labels.add("iteration3");
+        labels.add("iteration4");
+        labels.add("iteration5");
+        verifyTimingBundle(timing, labels);
+    }
+
+    @LargeTest
+    public void testLongSequence() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.beginSnapshot("testLongSequence");
+        mPerfCollector.startTiming("testLongSequenceTiming1");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration1");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration2");
+        sleepForRandomShortPeriod();
+        Bundle timing1 = mPerfCollector.stopTiming("iteration3");
+        sleepForRandomLongPeriod();
+
+        mPerfCollector.startTiming("testLongSequenceTiming2");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration4");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration5");
+        sleepForRandomShortPeriod();
+        Bundle timing2 = mPerfCollector.stopTiming("iteration6");
+        sleepForRandomLongPeriod();
+
+        mPerfCollector.startTiming("testLongSequenceTiming3");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration7");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration8");
+        sleepForRandomShortPeriod();
+        Bundle timing3 = mPerfCollector.stopTiming("iteration9");
+        sleepForRandomLongPeriod();
+
+        mPerfCollector.startTiming("testLongSequenceTiming4");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration10");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration11");
+        sleepForRandomShortPeriod();
+        Bundle timing4 = mPerfCollector.stopTiming("iteration12");
+        sleepForRandomLongPeriod();
+
+        mPerfCollector.startTiming("testLongSequenceTiming5");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration13");
+        sleepForRandomTinyPeriod();
+        mPerfCollector.addIteration("iteration14");
+        sleepForRandomShortPeriod();
+        Bundle timing5 = mPerfCollector.stopTiming("iteration15");
+        sleepForRandomLongPeriod();
+        Bundle snapshot1 = mPerfCollector.endSnapshot();
+        Bundle snapshot2 = writer.snapshotResults;
+
+        assertTrue(snapshot1.equals(snapshot2));
+        verifySnapshotBundle(snapshot1);
+
+        ArrayList<String> labels1 = new ArrayList<String>();
+        labels1.add("iteration1");
+        labels1.add("iteration2");
+        labels1.add("iteration3");
+        verifyTimingBundle(timing1, labels1);
+        ArrayList<String> labels2 = new ArrayList<String>();
+        labels2.add("iteration4");
+        labels2.add("iteration5");
+        labels2.add("iteration6");
+        verifyTimingBundle(timing2, labels2);
+        ArrayList<String> labels3 = new ArrayList<String>();
+        labels3.add("iteration7");
+        labels3.add("iteration8");
+        labels3.add("iteration9");
+        verifyTimingBundle(timing3, labels3);
+        ArrayList<String> labels4 = new ArrayList<String>();
+        labels4.add("iteration10");
+        labels4.add("iteration11");
+        labels4.add("iteration12");
+        verifyTimingBundle(timing4, labels4);
+        ArrayList<String> labels5 = new ArrayList<String>();
+        labels5.add("iteration13");
+        labels5.add("iteration14");
+        labels5.add("iteration15");
+        verifyTimingBundle(timing5, labels5);
+    }
+
+    /*
+     * Verify that snapshotting and timing do not interfere w/ each other,
+     * by staggering calls to snapshot and timing functions.
+     */
+    @LargeTest
+    public void testOutOfOrderSequence() {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.startTiming("testOutOfOrderSequenceTiming");
+        sleepForRandomShortPeriod();
+        mPerfCollector.beginSnapshot("testOutOfOrderSequenceSnapshot");
+        sleepForRandomShortPeriod();
+        Bundle timing1 = mPerfCollector.stopTiming("timing1");
+        sleepForRandomShortPeriod();
+        Bundle snapshot1 = mPerfCollector.endSnapshot();
+
+        Bundle timing2 = writer.timingResults;
+        Bundle snapshot2 = writer.snapshotResults;
+
+        assertTrue(snapshot1.equals(snapshot2));
+        verifySnapshotBundle(snapshot1);
+
+        assertTrue(timing1.equals(timing2));
+        ArrayList<String> labels = new ArrayList<String>();
+        labels.add("timing1");
+        verifyTimingBundle(timing1, labels);
+    }
+
+    private void sleepForRandomPeriod(int minDuration, int maxDuration) {
+        Random random = new Random();
+        int period = minDuration + random.nextInt(maxDuration - minDuration);
+        int slept = 0;
+        // Generate random positive amount of work, so cpu time is measurable in
+        // milliseconds
+        while (slept < period) {
+            int step = random.nextInt(minDuration/5);
+            try {
+                Thread.sleep(step);
+            } catch (InterruptedException e ) {
+                // eat the exception
+            }
+            slept += step;
+        }
+    }
+
+    private void sleepForRandomTinyPeriod() {
+        sleepForRandomPeriod(25, 50);
+    }
+
+    private void sleepForRandomShortPeriod() {
+        sleepForRandomPeriod(100, 250);
+    }
+
+    private void sleepForRandomLongPeriod() {
+        sleepForRandomPeriod(500, 1000);
+    }
+
+    private void verifySnapshotBundle(Bundle snapshot) {
+        assertTrue("At least 26 metrics collected", 26 <= snapshot.size());
+
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_CPU_TIME));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_EXECUTION_TIME));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME) > 0);
+
+        assertTrue(snapshot.containsKey(
+                PerformanceCollector.METRIC_KEY_PRE_RECEIVED_TRANSACTIONS));
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_PRE_SENT_TRANSACTIONS));
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_RECEIVED_TRANSACTIONS));
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_SENT_TRANSACTIONS));
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GC_INVOCATION_COUNT));
+
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_ALLOCATED));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_ALLOCATED) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_FREE));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_FREE) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_PRIVATE_DIRTY));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_PRIVATE_DIRTY) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_PSS));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_PSS) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_SHARED_DIRTY));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_SHARED_DIRTY) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_SIZE));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_SIZE) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_ALLOCATED));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_ALLOCATED) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_FREE));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_FREE) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_PRIVATE_DIRTY));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_PRIVATE_DIRTY) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_PSS));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_PSS) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_SHARED_DIRTY));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_SHARED_DIRTY) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_SIZE));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_SIZE) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_COUNT));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_COUNT) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_SIZE));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_SIZE) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_COUNT));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_COUNT) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_SIZE));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_SIZE) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_PRIVATE_DIRTY));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_PRIVATE_DIRTY) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_PSS));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_PSS) > 0);
+        assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_SHARED_DIRTY));
+        assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_SHARED_DIRTY) > 0);
+    }
+
+    private void verifyIterationBundle(Bundle iteration, String label) {
+        assertEquals(3, iteration.size());
+        assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_LABEL));
+        assertEquals(label, iteration.getString(PerformanceCollector.METRIC_KEY_LABEL));
+        assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_CPU_TIME));
+        assertTrue(iteration.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME) > 0);
+        assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_EXECUTION_TIME));
+        assertTrue(iteration.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME) > 0);
+    }
+
+    private void verifyTimingBundle(Bundle timing, ArrayList<String> labels) {
+        assertEquals(1, timing.size());
+        assertTrue(timing.containsKey(PerformanceCollector.METRIC_KEY_ITERATIONS));
+        ArrayList<Parcelable> iterations = timing.getParcelableArrayList(
+                PerformanceCollector.METRIC_KEY_ITERATIONS);
+        assertNotNull(iterations);
+        assertEquals(labels.size(), iterations.size());
+        for (int i = 0; i < labels.size(); i ++) {
+            Bundle iteration = (Bundle)iterations.get(i);
+            verifyIterationBundle(iteration, labels.get(i));
+        }
+    }
+
+    private Object readPrivateField(String fieldName, Object object) throws Exception {
+        Field f = object.getClass().getDeclaredField(fieldName);
+        f.setAccessible(true);
+        return f.get(object);
+    }
+
+    private class MockPerformanceResultsWriter implements PerformanceResultsWriter {
+
+        public String snapshotLabel;
+        public Bundle snapshotResults = new Bundle();
+        public String timingLabel;
+        public Bundle timingResults = new Bundle();
+
+        public void writeBeginSnapshot(String label) {
+            snapshotLabel = label;
+        }
+
+        public void writeEndSnapshot(Bundle results) {
+            snapshotResults = results;
+        }
+
+        public void writeStartTiming(String label) {
+            timingLabel = label;
+        }
+
+        public void writeStopTiming(Bundle results) {
+            timingResults = results;
+        }
+    }
+}