Merge "Adding some error handling to RemoteViewsAdapter, removing artificial loading delay."
diff --git a/Android.mk b/Android.mk
index 50e05d9..b4090a7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -436,10 +436,10 @@
 framework_docs_SDK_PREVIEW:=0
 
 ## Latest ADT version identifiers, for reference from published docs
-framework_docs_ADT_VERSION:=0.9.7
-framework_docs_ADT_DOWNLOAD:=ADT-0.9.7.zip
-framework_docs_ADT_BYTES:=8033750
-framework_docs_ADT_CHECKSUM:=de2431c8d4786d127ae5bfc95b4605df
+framework_docs_ADT_VERSION:=0.9.8
+framework_docs_ADT_DOWNLOAD:=ADT-0.9.8.zip
+framework_docs_ADT_BYTES:=8703591
+framework_docs_ADT_CHECKSUM:=22070f8e52924605a3b3abf87c1ba39f
 
 framework_docs_LOCAL_DROIDDOC_OPTIONS += \
 		-hdf sdk.version $(framework_docs_SDK_VERSION) \
diff --git a/api/current.xml b/api/current.xml
index bfbeaa2..fb1e626 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2088,6 +2088,39 @@
  visibility="public"
 >
 </field>
+<field name="actionBarTabBarStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843572"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarTabStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843571"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarTabTextStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843573"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="actionButtonPadding"
  type="int"
  transient="false"
@@ -2143,6 +2176,17 @@
  visibility="public"
 >
 </field>
+<field name="actionOverflowButtonStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843574"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="activityCloseEnterAnimation"
  type="int"
  transient="false"
@@ -104174,7 +104218,7 @@
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;id&quot;"
+ value="&quot;_id&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -150322,6 +150366,17 @@
  visibility="public"
 >
 </field>
+<field name="INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.media.action.MEDIA_PLAY_FROM_SEARCH&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="INTENT_ACTION_MEDIA_SEARCH"
  type="java.lang.String"
  transient="false"
@@ -238155,7 +238210,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index d1bc9bdc..69ad67e 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -520,6 +520,10 @@
         staggerDelay = 0;
 
         final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
+        if (!observer.isAlive()) {
+            // If the observer's not in a good state, skip the transition
+            return;
+        }
         int numChildren = parent.getChildCount();
 
         for (int i = 0; i < numChildren; ++i) {
@@ -607,7 +611,7 @@
         // layout listeners.
         observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             public boolean onPreDraw() {
-                observer.removeOnPreDrawListener(this);
+                parent.getViewTreeObserver().removeOnPreDrawListener(this);
                 int numChildren = parent.getChildCount();
                 for (int i = 0; i < numChildren; ++i) {
                     final View child = parent.getChildAt(i);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 05ebd07..61fc9e6 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -105,6 +105,14 @@
      * it with {@link android.content.Intent#getStringExtra(String)}.
      */
     public static final String EXTRA_EXTRA_INFO = "extraInfo";
+    /**
+     * The lookup key for an int that provides information about
+     * our connection to the internet at large.  0 indicates no connection,
+     * 100 indicates a great connection.  Retrieve it with
+     * {@link android.content.Intent@getIntExtra(String)}.
+     * {@hide}
+     */
+    public static final String EXTRA_INET_CONDITION = "inetCondition";
 
     /**
      * Broadcast Action: The setting for background data usage has changed
@@ -575,4 +583,16 @@
             return false;
         }
     }
+
+    /**
+     * @param networkType The type of network you want to report on
+     * @param percentage The quality of the connection 0 is bad, 100 is good
+     * {@hide}
+     */
+    public void reportInetCondition(int networkType, int percentage) {
+        try {
+            mService.reportInetCondition(networkType, percentage);
+        } catch (RemoteException e) {
+        }
+    }
 }
diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java
index cafe0f9..e04367a 100644
--- a/core/java/android/net/DownloadManager.java
+++ b/core/java/android/net/DownloadManager.java
@@ -21,6 +21,7 @@
 import android.database.Cursor;
 import android.database.CursorWrapper;
 import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
 import android.provider.Downloads;
 
 import java.io.File;
@@ -48,7 +49,7 @@
      * An identifier for a particular download, unique across the system.  Clients use this ID to
      * make subsequent calls related to the download.
      */
-    public final static String COLUMN_ID = "id";
+    public final static String COLUMN_ID = BaseColumns._ID;
 
     /**
      * The client-supplied title for this download.  This will be displayed in system notifications.
@@ -441,8 +442,22 @@
      * This class may be used to filter download manager queries.
      */
     public static class Query {
-        private Long mId;
+        /**
+         * Constant for use with {@link #orderBy}
+         * @hide
+         */
+        public static final int ORDER_ASCENDING = 1;
+
+        /**
+         * Constant for use with {@link #orderBy}
+         * @hide
+         */
+        public static final int ORDER_DESCENDING = 2;
+
+        private Long mId = null;
         private Integer mStatusFlags = null;
+        private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
+        private int mOrderDirection = ORDER_DESCENDING;
 
         /**
          * Include only the download with the given ID.
@@ -464,6 +479,32 @@
         }
 
         /**
+         * Change the sort order of the returned Cursor.
+         *
+         * @param column one of the COLUMN_* constants; currently, only
+         *         {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
+         *         supported.
+         * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
+         * @return this object
+         * @hide
+         */
+        public Query orderBy(String column, int direction) {
+            if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
+                throw new IllegalArgumentException("Invalid direction: " + direction);
+            }
+
+            if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
+                mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
+            } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
+                mOrderByColumn = Downloads.COLUMN_TOTAL_BYTES;
+            } else {
+                throw new IllegalArgumentException("Cannot order by " + column);
+            }
+            mOrderDirection = direction;
+            return this;
+        }
+
+        /**
          * Run this query using the given ContentResolver.
          * @param projection the projection to pass to ContentResolver.query()
          * @return the Cursor returned by ContentResolver.query()
@@ -497,7 +538,10 @@
                 }
                 selection = joinStrings(" OR ", parts);
             }
-            String orderBy = Downloads.COLUMN_LAST_MODIFICATION + " DESC";
+
+            String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
+            String orderBy = mOrderByColumn + " " + orderDirection;
+
             return resolver.query(uri, projection, selection, null, orderBy);
         }
 
@@ -567,6 +611,9 @@
      */
     public Cursor query(Query query) {
         Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS);
+        if (underlyingCursor == null) {
+            return null;
+        }
         return new CursorTranslator(underlyingCursor);
     }
 
@@ -608,7 +655,7 @@
         public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
             int index = getColumnIndex(columnName);
             if (index == -1) {
-                throw new IllegalArgumentException();
+                throw new IllegalArgumentException("No such column: " + columnName);
             }
             return index;
         }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index f31fb75..9f2fc17 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -78,4 +78,6 @@
     String[] getTetherableBluetoothRegexs();
 
     void requestNetworkTransitionWakelock(in String forWhom);
+
+    void reportInetCondition(int networkType, int percentage);
 }
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 420992b..5420d8f 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -71,6 +71,18 @@
     public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 7;
 
     /**
+     * msg.arg1 = network type
+     * msg.arg2 = condition (0 bad, 100 good)
+     */
+    public static final int EVENT_INET_CONDITION_CHANGE = 8;
+
+    /**
+     * msg.arg1 = network type
+     * msg.arg2 = default connection sequence number
+     */
+    public static final int EVENT_INET_CONDITION_HOLD_END = 9;
+
+    /**
      * -------------------------------------------------------------
      * Control Interface
      * -------------------------------------------------------------
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 9b2b090..d3718f8 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -75,6 +75,22 @@
     public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
 
     /**
+     * An intent to perform a search for music media and automatically play content from the
+     * result when possible. This can be fired, for example, by the result of a voice recognition
+     * command to listen to music.
+     * <p>
+     * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string
+     * that can contain any type of unstructured music search, like the name of an artist,
+     * an album, a song, a genre, or any combination of these.
+     * <p>
+     * Because this intent includes an open-ended unstructured search string, it makes the most
+     * sense for apps that can support large-scale search of music, such as services connected
+     * to an online database of music which can be streamed and played on the device.
+     */
+    public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
+            "android.media.action.MEDIA_PLAY_FROM_SEARCH";
+    
+    /**
      * The name of the Intent-extra used to define the artist
      */
     public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
@@ -257,7 +273,9 @@
      }
 
     /**
-     * Media provider interface used by MTP implementation.
+     * Media provider table containing an index of all files in the storage.
+     * This can be used by applications to find all documents of a particular type
+     * and is also used internally by the device side MTP implementation.
      * @hide
      */
     public static final class Files {
@@ -273,11 +291,22 @@
                     + "/file/" + fileId);
         }
 
-        // used for MTP GetObjectReferences and SetObjectReferences
-        public static final Uri getReferencesUri(String volumeName,
+        public static Uri getMtpObjectsUri(String volumeName) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                    "/object");
+        }
+
+        public static final Uri getMtpObjectsUri(String volumeName,
                 long fileId) {
             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
-                    + "/file/" + fileId + "/references");
+                    + "/object/" + fileId);
+        }
+
+        // Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
+        public static final Uri getMtpReferencesUri(String volumeName,
+                long fileId) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                    + "/object/" + fileId + "/references");
         }
 
         /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 567ee93..6af6a81 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3471,6 +3471,21 @@
         public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE =
                 "download_manager_max_bytes_over_mobile";
 
+        /**
+         * ms during which to consume extra events related to Inet connection condition
+         * after a transtion to fully-connected
+         * @hide
+         */
+        public static final String INET_CONDITION_DEBOUNCE_UP_DELAY =
+                "inet_condition_debounce_up_delay";
+
+        /**
+         * ms during which to consume extra events related to Inet connection condtion
+         * after a transtion to partly-connected
+         * @hide
+         */
+        public static final String INET_CONDITION_DEBOUNCE_DOWN_DELAY =
+                "inet_condition_debounce_down_delay";
 
         /**
          * @hide
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 48a2b72..fde3769 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -417,7 +417,13 @@
             mBluetoothService.sendUuidIntent(address);
         } else if (name.equals("Paired")) {
             if (propValues[1].equals("true")) {
-                mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
+                // If locally initiated pairing, we will
+                // not go to BOND_BONDED state until we have received a
+                // successful return value in onCreatePairedDeviceResult
+                if (null == mBluetoothService.getBondState().getPendingOutgoingBonding()) {
+                    mBluetoothService.getBondState().setBondState(address,
+                            BluetoothDevice.BOND_BONDED);
+                }
             } else {
                 mBluetoothService.getBondState().setBondState(address,
                         BluetoothDevice.BOND_NONE);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 57a72bf..2b083dc 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -178,6 +179,9 @@
         };
         
         final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
+            {
+                mRequestedFormat = PixelFormat.RGB_565;
+            }
 
             @Override
             public boolean onAllowLockCanvas() {
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 5be2a48..09cbbb8 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -246,8 +246,10 @@
     private void initPrefs(Context context) {
         final ContentResolver contentResolver = context.getContentResolver();
         mResolver = new WeakReference<ContentResolver>(contentResolver);
-        mObserver = new SettingsObserver();
-        contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
+        if (mObserver == null) {
+            mObserver = new SettingsObserver();
+            contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
+        }
 
         updatePrefs(contentResolver);
         mPrefsInited = true;
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index fb88c71..b1fdec8 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -41,6 +41,7 @@
     private static final int MAX_AGE_MILLISECONDS = 200;
     
     private static final int POINTER_POOL_CAPACITY = 20;
+    private static final int INVALID_POINTER = -1;
 
     private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
             Pools.finitePool(new PoolableManager<VelocityTracker>() {
@@ -76,6 +77,7 @@
     private Pointer mPointerListHead; // sorted by id in increasing order
     private int mLastTouchIndex;
     private int mGeneration;
+    private int mActivePointerId;
 
     private VelocityTracker mNext;
 
@@ -125,6 +127,7 @@
         
         mPointerListHead = null;
         mLastTouchIndex = 0;
+        mActivePointerId = INVALID_POINTER;
     }
     
     /**
@@ -180,6 +183,10 @@
                 // Pointer went down.  Add it to the list.
                 // Write a sentinel at the end of the pastTime trace so we will be able to
                 // tell when the trace started.
+                if (mActivePointerId == INVALID_POINTER) {
+                    // Congratulations! You're the new active pointer!
+                    mActivePointerId = pointerId;
+                }
                 pointer = obtainPointer();
                 pointer.id = pointerId;
                 pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
@@ -214,6 +221,7 @@
         previousPointer = null;
         for (Pointer pointer = mPointerListHead; pointer != null; ) {
             final Pointer nextPointer = pointer.next;
+            final int pointerId = pointer.id;
             if (pointer.generation != generation) {
                 // Pointer went up.  Remove it from the list.
                 if (previousPointer == null) {
@@ -222,6 +230,12 @@
                     previousPointer.next = nextPointer;
                 }
                 releasePointer(pointer);
+
+                if (pointerId == mActivePointerId) {
+                    // Pick a new active pointer. How is arbitrary.
+                    mActivePointerId = mPointerListHead != null ?
+                            mPointerListHead.id : INVALID_POINTER;
+                }
             } else {
                 previousPointer = pointer;
             }
@@ -334,7 +348,7 @@
      * @return The previously computed X velocity.
      */
     public float getXVelocity() {
-        Pointer pointer = getPointer(0);
+        Pointer pointer = getPointer(mActivePointerId);
         return pointer != null ? pointer.xVelocity : 0;
     }
     
@@ -345,7 +359,7 @@
      * @return The previously computed Y velocity.
      */
     public float getYVelocity() {
-        Pointer pointer = getPointer(0);
+        Pointer pointer = getPointer(mActivePointerId);
         return pointer != null ? pointer.yVelocity : 0;
     }
     
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5d258967..833fa70 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5094,20 +5094,23 @@
                 }
             }
             mMatrix.reset();
-            mMatrix.setTranslate(mTranslationX, mTranslationY);
-            mMatrix.preRotate(mRotation, mPivotX, mPivotY);
-            mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
-            if (nonzero(mRotationX) || nonzero(mRotationY)) {
+            if (!nonzero(mRotationX) && !nonzero(mRotationY)) {
+                mMatrix.setTranslate(mTranslationX, mTranslationY);
+                mMatrix.preRotate(mRotation, mPivotX, mPivotY);
+                mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+            } else {
                 if (mCamera == null) {
                     mCamera = new Camera();
                     matrix3D = new Matrix();
                 }
                 mCamera.save();
+                mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
                 mCamera.rotateX(mRotationX);
                 mCamera.rotateY(mRotationY);
+                mCamera.rotateZ(-mRotation);
                 mCamera.getMatrix(matrix3D);
                 matrix3D.preTranslate(-mPivotX, -mPivotY);
-                matrix3D.postTranslate(mPivotX, mPivotY);
+                matrix3D.postTranslate(mPivotX + mTranslationX, mPivotY + mTranslationY);
                 mMatrix.postConcat(matrix3D);
                 mCamera.restore();
             }
@@ -5148,7 +5151,8 @@
     }
 
     /**
-     * Sets the degrees that the view is rotated around the pivot point.
+     * Sets the degrees that the view is rotated around the pivot point. Increasing values
+     * result in clockwise rotation.
      *
      * @param rotation The degrees of rotation.
      * @see #getPivotX()
@@ -5177,7 +5181,9 @@
     }
 
     /**
-     * Sets the degrees that the view is rotated around the vertical axis through pivot point.
+     * Sets the degrees that the view is rotated around the vertical axis through the pivot point.
+     * Increasing values result in counter-clockwise rotation from the viewpoint of looking
+     * down the y axis.
      *
      * @param rotationY The degrees of Y rotation.
      * @see #getPivotX()
@@ -5206,7 +5212,9 @@
     }
 
     /**
-     * Sets the degrees that the view is rotated around the horizontal axis through pivot point.
+     * Sets the degrees that the view is rotated around the horizontal axis through the pivot point.
+     * Increasing values result in clockwise rotation from the viewpoint of looking down the
+     * x axis.
      *
      * @param rotationX The degrees of X rotation.
      * @see #getPivotX()
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index e8d96c5..052a38a 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -339,6 +339,25 @@
         return new ViewGroup.LayoutParams(0, 0);
     }
 
+    private void refreshChildren() {
+        for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
+            int index = modulo(i, mNumActiveViews);
+
+            // get the fresh child from the adapter
+            View updatedChild = mAdapter.getView(i, null, this);
+
+            if (mActiveViews[index] != null) {
+                FrameLayout fl = (FrameLayout) mActiveViews[index];
+                // flush out the old child
+                fl.removeAllViewsInLayout();
+                // add the new child to the frame, if it exists
+                if (updatedChild != null) {
+                    fl.addView(updatedChild);
+                }
+            }
+        }
+    }
+
     void showOnly(int childIndex, boolean animate, boolean onLayout) {
         if (mAdapter == null) return;
 
@@ -414,16 +433,19 @@
                     // We've cleared a spot for the new view. Get it from the adapter, add it
                     // and apply any transform / animation
                     View newView = mAdapter.getView(i, null, this);
+
+                    // We wrap the new view in a FrameLayout so as to respect the contract
+                    // with the adapter, that is, that we don't modify this view directly
+                    FrameLayout fl = new FrameLayout(mContext);
+
+                    // If the view from the adapter is null, we still keep an empty frame in place
                     if (newView != null) {
-                        // We wrap the new view in a FrameLayout so as to respect the contract
-                        // with the adapter, that is, that we don't modify this view directly
-                        FrameLayout fl = new FrameLayout(mContext);
-                        fl.addView(newView);
-                        mActiveViews[index] = fl;
-                        addChild(fl);
-                        applyTransformForChildAtIndex(fl, newRelativeIndex);
-                        animateViewForTransition(-1, newRelativeIndex, fl);
+                       fl.addView(newView);
                     }
+                    mActiveViews[index] = fl;
+                    addChild(fl);
+                    applyTransformForChildAtIndex(fl, newRelativeIndex);
+                    animateViewForTransition(-1, newRelativeIndex, fl);
                 }
                 mActiveViews[index].bringToFront();
             }
@@ -523,10 +545,12 @@
 
             // if the data changes, mWhichChild might be out of the bounds of the adapter
             // in this case, we reset mWhichChild to the beginning
-            if (mWhichChild >= mAdapter.getCount())
+            if (mWhichChild >= mAdapter.getCount()) {
                 mWhichChild = 0;
 
-            showOnly(mWhichChild, true, true);
+                showOnly(mWhichChild, true, true);
+            }
+            refreshChildren();
         }
 
         final int childCount = getChildCount();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dabe7ba..b8c9e72 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4448,7 +4448,7 @@
         }
 
         hideControllers();
-        
+
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_CENTER:
                 /*
@@ -5819,18 +5819,25 @@
     }
 
     private void convertFromViewportToContentCoordinates(Rect r) {
-        int paddingTop = getExtendedPaddingTop();
+        final int horizontalOffset = viewportToContentHorizontalOffset();
+        r.left += horizontalOffset;
+        r.right += horizontalOffset;
+
+        final int verticalOffset = viewportToContentVerticalOffset();
+        r.top += verticalOffset;
+        r.bottom += verticalOffset;
+    }
+
+    private int viewportToContentHorizontalOffset() {
+        return getCompoundPaddingLeft() - mScrollX;
+    }
+
+    private int viewportToContentVerticalOffset() {
+        int offset = getExtendedPaddingTop() - mScrollY;
         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
-            paddingTop += getVerticalOffset(false);
+            offset += getVerticalOffset(false);
         }
-        r.top += paddingTop;
-        r.bottom += paddingTop;
-
-        int paddingLeft = getCompoundPaddingLeft();
-        r.left += paddingLeft;
-        r.right += paddingLeft;
-
-        r.offset(-mScrollX, -mScrollY);
+        return offset;
     }
 
     @Override
@@ -5875,7 +5882,10 @@
      * Return true iff there is a selection inside this text view.
      */
     public boolean hasSelection() {
-        return getSelectionStart() != getSelectionEnd();
+        final int selectionStart = getSelectionStart();
+        final int selectionEnd = getSelectionEnd();
+
+        return selectionStart >= 0 && selectionStart != selectionEnd;
     }
 
     /**
@@ -6539,7 +6549,7 @@
         mShowCursor = SystemClock.uptimeMillis();
 
         ensureEndedBatchEdit();
-        
+
         if (focused) {
             int selStart = getSelectionStart();
             int selEnd = getSelectionEnd();
@@ -6547,6 +6557,8 @@
             if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
                 // Has to be done before onTakeFocus, which can be overloaded.
                 if (mLastTouchOffset >= 0) {
+                    // Can happen when a TextView is displayed after its content has been deleted.
+                    mLastTouchOffset = Math.min(mLastTouchOffset, mText.length());
                     Selection.setSelection((Spannable) mText, mLastTouchOffset);
                 }
 
@@ -6563,7 +6575,8 @@
                 // ExtractEditText clears focus, which gives focus to the ExtractEditText.
                 // This special case ensure that we keep current selection in that case.
                 // It would be better to know why the DecorView does not have focus at that time.
-                if (((this instanceof ExtractEditText) || mSelectionMoved) && selStart >= 0 && selEnd >= 0) {
+                if (((this instanceof ExtractEditText) || mSelectionMoved) &&
+                        selStart >= 0 && selEnd >= 0) {
                     /*
                      * Someone intentionally set the selection, so let them
                      * do whatever it is that they wanted to do instead of
@@ -6679,6 +6692,8 @@
 
         if (start == end) {
             if (start >= prevStart && start < prevEnd) {
+                // Restore previous selection
+                Selection.setSelection((Spannable)mText, prevStart, prevEnd);
                 // Tapping inside the selection displays the cut/copy/paste context menu.
                 showContextMenu();
                 return;
@@ -7068,7 +7083,7 @@
             return false;
         }
 
-        if (mText.length() > 0 && getSelectionStart() >= 0) {
+        if (mText.length() > 0 && hasSelection()) {
             if (mText instanceof Editable && mInput != null) {
                 return true;
             }
@@ -7082,7 +7097,7 @@
             return false;
         }
 
-        if (mText.length() > 0 && getSelectionStart() >= 0) {
+        if (mText.length() > 0 && hasSelection()) {
             return true;
         }
 
@@ -7203,6 +7218,49 @@
         int minOffset = selectionModifierCursorController.getMinTouchOffset();
         int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
 
+        if (minOffset == maxOffset) {
+            int offset = Math.max(0, Math.min(minOffset, mTransformed.length()));
+
+            // Tolerance, number of charaters around tapped position
+            final int range = 1;
+            final int max = mTransformed.length() - 1;
+
+            // 'Smart' word selection: detect position between words
+            for (int i = -range; i <= range; i++) {
+                int index = offset + i;
+                if (index >= 0 && index <= max) {
+                    if (Character.isSpaceChar(mTransformed.charAt(index))) {
+                        // Select current space
+                        selectionStart = index;
+                        selectionEnd = selectionStart + 1;
+
+                        // Extend selection to maximum space range
+                        while (selectionStart > 0 &&
+                                Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) {
+                            selectionStart--;
+                        }
+                        while (selectionEnd < max &&
+                                Character.isSpaceChar(mTransformed.charAt(selectionEnd))) {
+                            selectionEnd++;
+                        }
+
+                        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+                        return;
+                    }
+                }
+            }
+
+            // 'Smart' word selection: detect position at beginning or end of text.
+            if (offset <= range) {
+                Selection.setSelection((Spannable) mText, 0, 0);
+                return;
+            }
+            if (offset >= (max - range)) {
+                Selection.setSelection((Spannable) mText, max + 1, max + 1);
+                return;
+            }
+        }
+
         long wordLimits = getWordLimitsAt(minOffset);
         if (wordLimits >= 0) {
             selectionStart = (int) (wordLimits >>> 32);
@@ -7690,9 +7748,7 @@
             bounds.right = bounds.left + drawableWidth;
             bounds.bottom = bounds.top + drawableHeight;
 
-            int boundTopBefore = bounds.top;
             convertFromViewportToContentCoordinates(bounds);
-            mHotSpotVerticalPosition += bounds.top - boundTopBefore;
             mDrawable.setBounds(bounds);
             postInvalidate();
         }
@@ -7836,6 +7892,9 @@
                             mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
                             mOffsetY = mHandle.mHotSpotVerticalPosition - y;
 
+                            mOffsetX += viewportToContentHorizontalOffset();
+                            mOffsetY += viewportToContentVerticalOffset();
+
                             mOnDownTimerStart = event.getEventTime();
                         }
                         break;
@@ -8023,6 +8082,9 @@
                                     mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
                                     mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;
 
+                                    mOffsetX += viewportToContentHorizontalOffset();
+                                    mOffsetY += viewportToContentVerticalOffset();
+
                                     ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
                                 }
                             }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9ed3658..0361cf8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1378,13 +1378,13 @@
                 for (int i=0; i<N; i++) {
                     StopwatchTimer st = mPartialTimers.get(i);
                     if (st.mInList) {
-                        int myUTime = utime/num;
-                        int mySTime = stime/num;
-                        utime -= myUTime;
-                        stime -= mySTime;
-                        num--;
                         Uid uid = st.mUid;
                         if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+                            int myUTime = utime/num;
+                            int mySTime = stime/num;
+                            utime -= myUTime;
+                            stime -= mySTime;
+                            num--;
                             Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*");
                             proc.addCpuTimeLocked(myUTime, mySTime);
                             proc.addSpeedStepTimes(cpuSpeedTimes);
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index 3a04993..1e97cd6 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -41,7 +41,8 @@
 
     int mRequestedWidth = -1;
     int mRequestedHeight = -1;
-    int mRequestedFormat = PixelFormat.OPAQUE;
+    /** @hide */
+    protected int mRequestedFormat = PixelFormat.OPAQUE;
     int mRequestedType = -1;
 
     long mLastLockTime = 0;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index e064e2c..c4b6214 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -213,13 +213,11 @@
 
     private class OverflowMenuButton extends ImageButton {
         public OverflowMenuButton(Context context) {
-            super(context, null, com.android.internal.R.attr.actionButtonStyle);
+            super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
 
             final Resources res = context.getResources();
             setClickable(true);
             setFocusable(true);
-            setContentDescription(res.getString(com.android.internal.R.string.more_item_label));
-            setImageDrawable(res.getDrawable(com.android.internal.R.drawable.ic_menu_more));
             setVisibility(VISIBLE);
             setEnabled(true);
         }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 6b3d353..308a709 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -371,7 +371,8 @@
                 break;
             case ActionBar.NAVIGATION_MODE_TABS:
                 mTabScrollView = new HorizontalScrollView(getContext());
-                mTabLayout = new LinearLayout(getContext());
+                mTabLayout = new LinearLayout(getContext(), null,
+                        com.android.internal.R.attr.actionBarTabBarStyle);
                 mTabScrollView.addView(mTabLayout);
                 addView(mTabScrollView);
                 break;
@@ -609,7 +610,7 @@
             if (mTabScrollView != null) {
                 mTabScrollView.measure(
                         MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
             }
             break;
         }
@@ -711,7 +712,7 @@
         private ActionBar.Tab mTab;
 
         public TabView(Context context, ActionBar.Tab tab) {
-            super(context);
+            super(context, null, com.android.internal.R.attr.actionBarTabStyle);
             mTab = tab;
 
             final View custom = tab.getCustomView();
@@ -734,7 +735,8 @@
                 }
 
                 if (text != null) {
-                    TextView textView = new TextView(context);
+                    TextView textView = new TextView(context, null,
+                            com.android.internal.R.attr.actionBarTabTextStyle);
                     textView.setText(text);
                     textView.setSingleLine();
                     textView.setEllipsize(TruncateAt.END);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 95bb24f..880fb6e 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -254,7 +254,9 @@
 

 static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {

 #ifdef USE_OPENGL_RENDERER

-    android::uirenderer::Caches::getInstance().textureCache.remove(bitmap);

+    if (android::uirenderer::Caches::hasInstance()) {

+        android::uirenderer::Caches::getInstance().textureCache.remove(bitmap);

+    }

 #endif

     delete bitmap;

 }

diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
index b8b2a10..abe33f4 100644
--- a/core/jni/android/graphics/Path.cpp
+++ b/core/jni/android/graphics/Path.cpp
@@ -26,12 +26,19 @@
 
 #include "SkPath.h"
 
+#include <Caches.h>
+
 namespace android {
 
 class SkPathGlue {
 public:
 
     static void finalizer(JNIEnv* env, jobject clazz, SkPath* obj) {
+#ifdef USE_OPENGL_RENDERER
+        if (android::uirenderer::Caches::hasInstance()) {
+            android::uirenderer::Caches::getInstance().pathCache.remove(obj);
+        }
+#endif
         delete obj;
     }
 
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index cb1c333..9202429 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -9,6 +9,7 @@
 #include "SkXfermode.h"
 
 #include <SkiaShader.h>
+#include <Caches.h>
 
 using namespace android::uirenderer;
 
@@ -52,6 +53,11 @@
 
 static void Shader_destructor(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* skiaShader)
 {
+#ifdef USE_OPENGL_RENDERER
+    if (android::uirenderer::Caches::hasInstance()) {
+        android::uirenderer::Caches::getInstance().gradientCache.remove(shader);
+    }
+#endif
     delete skiaShader;
     shader->safeUnref();
 }
diff --git a/core/res/res/drawable-mdpi/minitab_lt_focus.9.png b/core/res/res/drawable-mdpi/minitab_lt_focus.9.png
new file mode 100644
index 0000000..415c571
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_press.9.png b/core/res/res/drawable-mdpi/minitab_lt_press.9.png
new file mode 100644
index 0000000..4166543
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_press.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_selected.9.png b/core/res/res/drawable-mdpi/minitab_lt_selected.9.png
new file mode 100644
index 0000000..fefa27e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png b/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png
new file mode 100644
index 0000000..0051cd5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png b/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png
new file mode 100644
index 0000000..69444dd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png
Binary files differ
diff --git a/core/res/res/drawable/minitab_lt.xml b/core/res/res/drawable/minitab_lt.xml
new file mode 100644
index 0000000..aeea97c
--- /dev/null
+++ b/core/res/res/drawable/minitab_lt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:state_selected="true"
+          android:drawable="@drawable/minitab_lt_press" />
+    <item android:state_selected="true"
+          android:drawable="@drawable/minitab_lt_selected" />
+    <item android:state_pressed="true"
+          android:drawable="@drawable/minitab_lt_unselected_press" />
+    <item android:drawable="@drawable/minitab_lt_unselected" />
+</selector>
diff --git a/core/res/res/values-mcc450-ko/config.xml b/core/res/res/values-mcc450-ko/config.xml
new file mode 100644
index 0000000..335b967
--- /dev/null
+++ b/core/res/res/values-mcc450-ko/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+    <string name="gsm_alphabet_default_charset">euc-kr</string>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b476b44..f466e82 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -473,6 +473,11 @@
         <eat-comment />
         <!-- Default amount of padding to use between action buttons. -->
         <attr name="actionButtonPadding" format="dimension" />
+        <!-- Default style for tabs within an action bar -->
+        <attr name="actionBarTabStyle" format="reference" />
+        <attr name="actionBarTabBarStyle" format="reference" />
+        <attr name="actionBarTabTextStyle" format="reference" />
+        <attr name="actionOverflowButtonStyle" format="reference" />
 
         <!-- =================== -->
         <!-- Action mode styles  -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 988176d..ac5828f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -80,6 +80,10 @@
          for backward compatibility with apps that require external storage. -->
     <bool name="config_emulateExternalStorage">false</bool>
 
+    <!-- Set to true if external storage is case sensitive.
+         Typically external storage is FAT, which is case insensitive. -->
+    <bool name="config_caseSensitiveExternalStorage">false</bool>
+
     <!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
          Please don't copy them, copy anything else. -->
 
@@ -401,4 +405,8 @@
 
     <!-- IP address of the dns server to use if nobody else suggests one -->
     <string name="config_default_dns_server">8.8.8.8</string>
+
+    <!-- The default character set for GsmAlphabet -->
+    <!-- Empty string means MBCS is not considered -->
+    <string name="gsm_alphabet_default_charset"></string>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7bb56bd..7f168a1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1335,6 +1335,10 @@
   <public type="attr" name="listChoiceBackgroundIndicator" />
   <public type="attr" name="spinnerMode" />
   <public type="attr" name="animateLayoutChanges" />
+  <public type="attr" name="actionBarTabStyle" />
+  <public type="attr" name="actionBarTabBarStyle" />
+  <public type="attr" name="actionBarTabTextStyle" />
+  <public type="attr" name="actionOverflowButtonStyle" />
 
   <public type="anim" name="animator_fade_in" />
   <public type="anim" name="animator_fade_out" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 6964808..5c3870d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -885,12 +885,32 @@
         <item name="android:divider">@android:drawable/action_bar_divider</item>
         <item name="android:height">?android:attr/windowActionBarSize</item>
         <item name="android:paddingLeft">3dip</item>
-        <item name="android:paddingTop">3dip</item>
+        <item name="android:paddingTop">0dip</item>
         <item name="android:paddingRight">3dip</item>
-        <item name="android:paddingBottom">3dip</item>
+        <item name="android:paddingBottom">0dip</item>
     </style>
 
     <style name="Widget.ActionButton">
         <item name="android:background">@null</item>
     </style>
+
+    <style name="Widget.ActionButton.Overflow">
+        <item name="android:src">@drawable/ic_menu_more</item>
+        <item name="android:contentDescription">@string/more_item_label</item>
+    </style>
+
+    <style name="Widget.ActionBarView_TabView">
+        <item name="android:background">@drawable/minitab_lt</item>
+        <item name="android:paddingLeft">4dip</item>
+        <item name="android:paddingRight">4dip</item>
+    </style>
+
+    <style name="Widget.ActionBarView_TabBar">
+    </style>
+
+    <style name="Widget.ActionBarView_TabText">
+        <item name="android:textAppearance">@style/TextAppearance.Widget.TextView.PopupMenu</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textSize">18sp</item>
+    </style>
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 6ed7b71..7b3a47c 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -183,6 +183,7 @@
         <item name="dropDownSpinnerStyle">@android:style/Widget.Spinner.DropDown</item>
         <item name="actionDropDownStyle">@android:style/Widget.Spinner.DropDown</item>
         <item name="actionButtonStyle">@android:style/Widget.ActionButton</item>
+        <item name="actionOverflowButtonStyle">@android:style/Widget.ActionButton.Overflow</item>
         <item name="starStyle">@android:style/Widget.CompoundButton.Star</item>
         <item name="tabWidgetStyle">@android:style/Widget.TabWidget</item>
         <item name="textViewStyle">@android:style/Widget.TextView</item>
@@ -199,6 +200,9 @@
         <item name="quickContactBadgeStyleSmallWindowSmall">@android:style/Widget.QuickContactBadgeSmall.WindowSmall</item>
         <item name="quickContactBadgeStyleSmallWindowMedium">@android:style/Widget.QuickContactBadgeSmall.WindowMedium</item>
         <item name="quickContactBadgeStyleSmallWindowLarge">@android:style/Widget.QuickContactBadgeSmall.WindowLarge</item>
+        <item name="actionBarTabStyle">@style/Widget.ActionBarView_TabView</item>
+        <item name="actionBarTabBarStyle">@style/Widget.ActionBarView_TabBar</item>
+        <item name="actionBarTabTextStyle">@style/Widget.ActionBarView_TabText</item>
         
         <!-- Preference styles -->
         <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 693ef18..b496805 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -12,7 +12,7 @@
 	$(call all-java-files-under, EnabledTestApp/src)
 
 LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common frameworks-core-util-lib
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksCoreTests
 
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index ce73ae1..f09421b 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -36,12 +36,16 @@
             android:description="@string/permdesc_testDenied" />
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
     <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
+    <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
     <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
diff --git a/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java
new file mode 100644
index 0000000..ee0f5f1
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java
@@ -0,0 +1,888 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.DownloadManager;
+import android.net.NetworkInfo;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.wifi.WifiManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.provider.Settings;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.concurrent.TimeoutException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Vector;
+
+import junit.framework.AssertionFailedError;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+
+/**
+ * Base class for Instrumented tests for the Download Manager.
+ */
+public class DownloadManagerBaseTest extends InstrumentationTestCase {
+
+    protected DownloadManager mDownloadManager = null;
+    protected MockWebServer mServer = null;
+    protected String mFileType = "text/plain";
+    protected Context mContext = null;
+    protected static final int DEFAULT_FILE_SIZE = 130 * 1024;  // 130kb
+    protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024;
+
+    protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest";
+    protected static final int HTTP_OK = 200;
+    protected static final int HTTP_PARTIAL_CONTENT = 206;
+    protected static final int HTTP_NOT_FOUND = 404;
+    protected static final int HTTP_SERVICE_UNAVAILABLE = 503;
+    protected String DEFAULT_FILENAME = "somefile.txt";
+
+    protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000;  // 2 minutes
+    protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000;  // 5 seconds
+
+    protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000;  // 1 second
+    protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes
+
+    // Just a few popular file types used to return from a download
+    protected enum DownloadFileType {
+        PLAINTEXT,
+        APK,
+        GIF,
+        GARBAGE,
+        UNRECOGNIZED,
+        ZIP
+    }
+
+    protected enum DataType {
+        TEXT,
+        BINARY
+    }
+
+    public static class LoggingRng extends Random {
+
+        /**
+         * Constructor
+         *
+         * Creates RNG with self-generated seed value.
+         */
+        public LoggingRng() {
+            this(SystemClock.uptimeMillis());
+        }
+
+        /**
+         * Constructor
+         *
+         * Creats RNG with given initial seed value
+
+         * @param seed The initial seed value
+         */
+        public LoggingRng(long seed) {
+            super(seed);
+            Log.i(LOG_TAG, "Seeding RNG with value: " + seed);
+        }
+    }
+
+    public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver {
+        private volatile int mNumDownloadsCompleted = 0;
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
+                ++mNumDownloadsCompleted;
+                Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " +
+                        intent.getAction() + " --> total count: " + mNumDownloadsCompleted);
+            }
+        }
+
+        /**
+         * Gets the number of times the {@link #onReceive} callback has been called for the
+         * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of
+         * downloads completed thus far.
+         *
+         * @return the number of downloads completed so far.
+         */
+        public int numDownloadsCompleted() {
+            return mNumDownloadsCompleted;
+        }
+    }
+
+    public static class WiFiChangedReceiver extends BroadcastReceiver {
+        private Context mContext = null;
+
+        /**
+         * Constructor
+         *
+         * Sets the current state of WiFi.
+         *
+         * @param context The current app {@link Context}.
+         */
+        public WiFiChangedReceiver(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction());
+                synchronized (this) {
+                    this.notify();
+                }
+            }
+        }
+
+        /**
+         * Gets the current state of WiFi.
+         *
+         * @return Returns true if WiFi is on, false otherwise.
+         */
+        public boolean getWiFiIsOn() {
+            ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+            NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+            return info.isConnected();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        mContext = getInstrumentation().getContext();
+        mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+        mServer = new MockWebServer();
+        // Note: callers overriding this should call mServer.play() with the desired port #
+    }
+
+    /**
+     * Helper to enqueue a response from the MockWebServer.
+     *
+     * @param status The HTTP status code to return for this response
+     * @param body The body to return in this response
+     * @return Returns the mock web server response that was queued (which can be modified)
+     */
+    protected MockResponse enqueueResponse(int status, byte[] body) {
+        return doEnqueueResponse(status).setBody(body);
+
+    }
+
+    /**
+     * Helper to enqueue a response from the MockWebServer.
+     *
+     * @param status The HTTP status code to return for this response
+     * @param bodyFile The body to return in this response
+     * @return Returns the mock web server response that was queued (which can be modified)
+     */
+    protected MockResponse enqueueResponse(int status, File bodyFile) {
+        return doEnqueueResponse(status).setBody(bodyFile);
+    }
+
+    /**
+     * Helper for enqueue'ing a response from the MockWebServer.
+     *
+     * @param status The HTTP status code to return for this response
+     * @return Returns the mock web server response that was queued (which can be modified)
+     */
+    protected MockResponse doEnqueueResponse(int status) {
+        MockResponse response = new MockResponse().setResponseCode(status);
+        response.addHeader("Content-type", mFileType);
+        mServer.enqueue(response);
+        return response;
+    }
+
+    /**
+     * Helper to generate a random blob of bytes.
+     *
+     * @param size The size of the data to generate
+     * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
+     *         {@link DataType.BINARY}.
+     * @return The random data that is generated.
+     */
+    protected byte[] generateData(int size, DataType type) {
+        return generateData(size, type, null);
+    }
+
+    /**
+     * Helper to generate a random blob of bytes using a given RNG.
+     *
+     * @param size The size of the data to generate
+     * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
+     *         {@link DataType.BINARY}.
+     * @param rng (optional) The RNG to use; pass null to use
+     * @return The random data that is generated.
+     */
+    protected byte[] generateData(int size, DataType type, Random rng) {
+        int min = Byte.MIN_VALUE;
+        int max = Byte.MAX_VALUE;
+
+        // Only use chars in the HTTP ASCII printable character range for Text
+        if (type == DataType.TEXT) {
+            min = 32;
+            max = 126;
+        }
+        byte[] result = new byte[size];
+        Log.i(LOG_TAG, "Generating data of size: " + size);
+
+        if (rng == null) {
+            rng = new LoggingRng();
+        }
+
+        for (int i = 0; i < size; ++i) {
+            result[i] = (byte) (min + rng.nextInt(max - min + 1));
+        }
+        return result;
+    }
+
+    /**
+     * Helper to verify the size of a file.
+     *
+     * @param pfd The input file to compare the size of
+     * @param size The expected size of the file
+     */
+    protected void verifyFileSize(ParcelFileDescriptor pfd, long size) {
+        assertEquals(pfd.getStatSize(), size);
+    }
+
+    /**
+     * Helper to verify the contents of a downloaded file versus a byte[].
+     *
+     * @param actual The file of whose contents to verify
+     * @param expected The data we expect to find in the aforementioned file
+     * @throws IOException if there was a problem reading from the file
+     */
+    protected void verifyFileContents(ParcelFileDescriptor actual, byte[] expected)
+            throws IOException {
+        AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(actual);
+        long fileSize = actual.getStatSize();
+
+        assertTrue(fileSize <= Integer.MAX_VALUE);
+        assertEquals(expected.length, fileSize);
+
+        byte[] actualData = new byte[expected.length];
+        assertEquals(input.read(actualData), fileSize);
+        compareByteArrays(actualData, expected);
+    }
+
+    /**
+     * Helper to compare 2 byte arrays.
+     *
+     * @param actual The array whose data we want to verify
+     * @param expected The array of data we expect to see
+     */
+    protected void compareByteArrays(byte[] actual, byte[] expected) {
+        assertEquals(actual.length, expected.length);
+        int length = actual.length;
+        for (int i = 0; i < length; ++i) {
+            // assert has a bit of overhead, so only do the assert when the values are not the same
+            if (actual[i] != expected[i]) {
+                fail("Byte arrays are not equal.");
+            }
+        }
+    }
+
+    /**
+     * Verifies the contents of a downloaded file versus the contents of a File.
+     *
+     * @param pfd The file whose data we want to verify
+     * @param file The file containing the data we expect to see in the aforementioned file
+     * @throws IOException If there was a problem reading either of the two files
+     */
+    protected void verifyFileContents(ParcelFileDescriptor pfd, File file) throws IOException {
+        byte[] actual = new byte[FILE_BLOCK_READ_SIZE];
+        byte[] expected = new byte[FILE_BLOCK_READ_SIZE];
+
+        AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+
+        assertEquals(file.length(), pfd.getStatSize());
+
+        DataInputStream inFile = new DataInputStream(new FileInputStream(file));
+        int actualRead = 0;
+        int expectedRead = 0;
+
+        while (((actualRead = input.read(actual)) != -1) &&
+                ((expectedRead = inFile.read(expected)) != -1)) {
+            assertEquals(actualRead, expectedRead);
+            compareByteArrays(actual, expected);
+        }
+    }
+
+    /**
+     * Sets the MIME type of file that will be served from the mock server
+     *
+     * @param type The MIME type to return from the server
+     */
+    protected void setServerMimeType(DownloadFileType type) {
+        mFileType = getMimeMapping(type);
+    }
+
+    /**
+     * Gets the MIME content string for a given type
+     *
+     * @param type The MIME type to return
+     * @return the String representation of that MIME content type
+     */
+    protected String getMimeMapping(DownloadFileType type) {
+        switch (type) {
+            case APK:
+                return "application/vnd.android.package-archive";
+            case GIF:
+                return "image/gif";
+            case ZIP:
+                return "application/x-zip-compressed";
+            case GARBAGE:
+                return "zip\\pidy/doo/da";
+            case UNRECOGNIZED:
+                return "application/new.undefined.type.of.app";
+        }
+        return "text/plain";
+    }
+
+    /**
+     * Gets the Uri that should be used to access the mock server
+     *
+     * @param filename The name of the file to try to retrieve from the mock server
+     * @return the Uri to use for access the file on the mock server
+     */
+    protected Uri getServerUri(String filename) throws Exception {
+        URL url = mServer.getUrl("/" + filename);
+        return Uri.parse(url.toString());
+    }
+
+   /**
+    * Gets the Uri that should be used to access the mock server
+    *
+    * @param filename The name of the file to try to retrieve from the mock server
+    * @return the Uri to use for access the file on the mock server
+    */
+    protected void logDBColumnData(Cursor cursor, String column) {
+        int index = cursor.getColumnIndex(column);
+        Log.i(LOG_TAG, "columnName: " + column);
+        Log.i(LOG_TAG, "columnValue: " + cursor.getString(index));
+    }
+
+    /**
+     * Helper to create and register a new MultipleDownloadCompletedReciever
+     *
+     * This is used to track many simultaneous downloads by keeping count of all the downloads
+     * that have completed.
+     *
+     * @return A new receiver that records and can be queried on how many downloads have completed.
+     */
+    protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() {
+        MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver();
+        mContext.registerReceiver(receiver, new IntentFilter(
+                DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+        return receiver;
+    }
+
+    /**
+     * Helper to verify a standard single-file download from the mock server, and clean up after
+     * verification
+     *
+     * Note that this also calls the Download manager's remove, which cleans up the file from cache.
+     *
+     * @param requestId The id of the download to remove
+     * @param fileData The data to verify the file contains
+     */
+    protected void verifyAndCleanupSingleFileDownload(long requestId, byte[] fileData)
+            throws Exception {
+        int fileSize = fileData.length;
+        ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(requestId);
+        Cursor cursor = mDownloadManager.query(new Query().setFilterById(requestId));
+
+        try {
+            assertEquals(1, cursor.getCount());
+            assertTrue(cursor.moveToFirst());
+
+            mServer.checkForExceptions();
+
+            verifyFileSize(pfd, fileSize);
+            verifyFileContents(pfd, fileData);
+        } finally {
+            pfd.close();
+            cursor.close();
+            mDownloadManager.remove(requestId);
+        }
+    }
+
+    /**
+     * Enables or disables WiFi.
+     *
+     * Note: Needs the following permissions:
+     *  android.permission.ACCESS_WIFI_STATE
+     *  android.permission.CHANGE_WIFI_STATE
+     * @param enable true if it should be enabled, false if it should be disabled
+     */
+    protected void setWiFiStateOn(boolean enable) throws Exception {
+        WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
+
+        manager.setWifiEnabled(enable);
+
+        String timeoutMessage = "Timed out waiting for Wifi to be "
+            + (enable ? "enabled!" : "disabled!");
+
+        WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext);
+        mContext.registerReceiver(receiver, new IntentFilter(
+                ConnectivityManager.CONNECTIVITY_ACTION));
+
+        synchronized (receiver) {
+            long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME;
+            boolean timedOut = false;
+
+            while (receiver.getWiFiIsOn() != enable && !timedOut) {
+                try {
+                    receiver.wait(DEFAULT_MAX_WAIT_TIME);
+
+                    if (SystemClock.elapsedRealtime() > timeoutTime) {
+                        timedOut = true;
+                    }
+                }
+                catch (InterruptedException e) {
+                    // ignore InterruptedExceptions
+                }
+            }
+            if (timedOut) {
+                fail(timeoutMessage);
+            }
+        }
+        assertEquals(enable, receiver.getWiFiIsOn());
+    }
+
+    /**
+     * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent
+     * indicating that the mode has changed.
+     *
+     * Note: Needs the following permission:
+     *  android.permission.WRITE_SETTINGS
+     * @param enable true if airplane mode should be ON, false if it should be OFF
+     */
+    protected void setAirplaneModeOn(boolean enable) throws Exception {
+        int state = enable ? 1 : 0;
+
+        // Change the system setting
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
+                state);
+
+        String timeoutMessage = "Timed out waiting for airplane mode to be " +
+                (enable ? "enabled!" : "disabled!");
+
+        // wait for airplane mode to change state
+        int currentWaitTime = 0;
+        while (Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.AIRPLANE_MODE_ON, -1) != state) {
+            timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME,
+                    timeoutMessage);
+        }
+
+        // Post the intent
+        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", true);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Helper to create a large file of random data on the SD card.
+     *
+     * @param filename (optional) The name of the file to create on the SD card; pass in null to
+     *          use a default temp filename.
+     * @param type The type of file to create
+     * @param subdirectory If not null, the subdirectory under the SD card where the file should go
+     * @return The File that was created
+     * @throws IOException if there was an error while creating the file.
+     */
+    protected File createFileOnSD(String filename, long fileSize, DataType type,
+            String subdirectory) throws IOException {
+
+        // Build up the file path and name
+        String sdPath = Environment.getExternalStorageDirectory().getPath();
+        StringBuilder fullPath = new StringBuilder(sdPath);
+        if (subdirectory != null) {
+            fullPath.append(File.separatorChar).append(subdirectory);
+        }
+
+        File file = null;
+        if (filename == null) {
+            file = File.createTempFile("DMTEST_", null, new File(fullPath.toString()));
+        }
+        else {
+            fullPath.append(File.separatorChar).append(filename);
+            file = new File(fullPath.toString());
+            file.createNewFile();
+        }
+
+        // Fill the file with random data
+        DataOutputStream output = new DataOutputStream(new FileOutputStream(file));
+        final int CHUNK_SIZE = 1000000;  // copy random data in 1000000-char chunks
+        long remaining = fileSize;
+        int nextChunkSize = CHUNK_SIZE;
+        byte[] randomData = null;
+        Random rng = new LoggingRng();
+
+        try {
+            while (remaining > 0) {
+                if (remaining < CHUNK_SIZE) {
+                    nextChunkSize = (int)remaining;
+                    remaining = 0;
+                }
+                else {
+                    remaining -= CHUNK_SIZE;
+                }
+
+                randomData = generateData(nextChunkSize, type, rng);
+                output.write(randomData);
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Error writing to file " + file.getAbsolutePath());
+            file.delete();
+            throw e;
+        } finally {
+            output.close();
+        }
+        return file;
+    }
+
+    /**
+     * Helper to wait for a particular download to finish, or else a timeout to occur
+     *
+     * @param id The download id to query on (wait for)
+     */
+    protected void waitForDownloadOrTimeout(long id) throws TimeoutException,
+            InterruptedException {
+        waitForDownloadOrTimeout(id, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+    }
+
+    /**
+     * Helper to wait for a particular download to finish, or else a timeout to occur
+     *
+     * @param id The download id to query on (wait for)
+     * @param poll The amount of time to wait
+     * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+     */
+    protected void waitForDownloadOrTimeout(long id, long poll, long timeoutMillis)
+            throws TimeoutException, InterruptedException {
+        doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
+    }
+
+    /**
+     * Helper to wait for all downloads to finish, or else a specified timeout to occur
+     *
+     * @param poll The amount of time to wait
+     * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+     */
+    protected void waitForDownloadsOrTimeout(long poll, long timeoutMillis) throws TimeoutException,
+            InterruptedException {
+        doWaitForDownloadsOrTimeout(new Query(), poll, timeoutMillis);
+    }
+
+    /**
+     * Helper to wait for all downloads to finish, or else a timeout to occur, but does not throw
+     *
+     * @param id The id of the download to query against
+     * @param poll The amount of time to wait
+     * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+     * @return true if download completed successfully (didn't timeout), false otherwise
+     */
+    protected boolean waitForDownloadOrTimeoutNoThrow(long id, long poll, long timeoutMillis) {
+        try {
+            doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
+        } catch (TimeoutException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Helper function to synchronously wait, or timeout if the maximum threshold has been exceeded.
+     *
+     * @param currentTotalWaitTime The total time waited so far
+     * @param poll The amount of time to wait
+     * @param maxTimeoutMillis The total wait time threshold; if we've waited more than this long,
+     *          we timeout and fail
+     * @param timedOutMessage The message to display in the failure message if we timeout
+     * @return The new total amount of time we've waited so far
+     * @throws TimeoutException if timed out waiting for SD card to mount
+     */
+    protected int timeoutWait(int currentTotalWaitTime, long poll, long maxTimeoutMillis,
+            String timedOutMessage) throws TimeoutException {
+        long now = SystemClock.elapsedRealtime();
+        long end = now + poll;
+
+        // if we get InterruptedException's, ignore them and just keep sleeping
+        while (now < end) {
+            try {
+                Thread.sleep(end - now);
+            } catch (InterruptedException e) {
+                // ignore interrupted exceptions
+            }
+            now = SystemClock.elapsedRealtime();
+        }
+
+        currentTotalWaitTime += poll;
+        if (currentTotalWaitTime > maxTimeoutMillis) {
+            throw new TimeoutException(timedOutMessage);
+        }
+        return currentTotalWaitTime;
+    }
+
+    /**
+     * Helper to wait for all downloads to finish, or else a timeout to occur
+     *
+     * @param query The query to pass to the download manager
+     * @param poll The poll time to wait between checks
+     * @param timeoutMillis The max amount of time (in ms) to wait for the download(s) to complete
+     */
+    protected void doWaitForDownloadsOrTimeout(Query query, long poll, long timeoutMillis)
+            throws TimeoutException {
+        int currentWaitTime = 0;
+        while (true) {
+            query.setFilterByStatus(DownloadManager.STATUS_PENDING | DownloadManager.STATUS_PAUSED
+                    | DownloadManager.STATUS_RUNNING);
+            Cursor cursor = mDownloadManager.query(query);
+
+            try {
+                // If we've finished the downloads then we're done
+                if (cursor.getCount() == 0) {
+                    break;
+                }
+                currentWaitTime = timeoutWait(currentWaitTime, poll, timeoutMillis,
+                        "Timed out waiting for all downloads to finish");
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Synchronously waits for external store to be mounted (eg: SD Card).
+     *
+     * @throws InterruptedException if interrupted
+     * @throws Exception if timed out waiting for SD card to mount
+     */
+    protected void waitForExternalStoreMount() throws Exception {
+        String extStorageState = Environment.getExternalStorageState();
+        int currentWaitTime = 0;
+        while (!extStorageState.equals(Environment.MEDIA_MOUNTED)) {
+            Log.i(LOG_TAG, "Waiting for SD card...");
+            currentWaitTime = timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME,
+                    DEFAULT_MAX_WAIT_TIME, "Timed out waiting for SD Card to be ready!");
+            extStorageState = Environment.getExternalStorageState();
+        }
+    }
+
+    /**
+     * Synchronously waits for a download to start.
+     *
+     * @param dlRequest the download request id used by Download Manager to track the download.
+     * @throws Exception if timed out while waiting for SD card to mount
+     */
+    protected void waitForDownloadToStart(long dlRequest) throws Exception {
+        Cursor cursor = getCursor(dlRequest);
+        try {
+            int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+            int value = cursor.getInt(columnIndex);
+            int currentWaitTime = 0;
+
+            while (value != DownloadManager.STATUS_RUNNING &&
+                    (value != DownloadManager.STATUS_FAILED) &&
+                    (value != DownloadManager.STATUS_SUCCESSFUL)) {
+                Log.i(LOG_TAG, "Waiting for download to start...");
+                currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+                        MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download to start!");
+                cursor.requery();
+                assertTrue(cursor.moveToFirst());
+                columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+                value = cursor.getInt(columnIndex);
+            }
+            assertFalse("Download failed immediately after start",
+                    value == DownloadManager.STATUS_FAILED);
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Synchronously waits for a file to increase in size (such as to monitor that a download is
+     * progressing).
+     *
+     * @param file The file whose size to track.
+     * @throws Exception if timed out while waiting for the file to grow in size.
+     */
+    protected void waitForFileToGrow(File file) throws Exception {
+        int currentWaitTime = 0;
+
+        // File may not even exist yet, so wait until it does (or we timeout)
+        while (!file.exists()) {
+            Log.i(LOG_TAG, "Waiting for file to exist...");
+            currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+                    MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be created.");
+        }
+
+        // Get original file size...
+        long originalSize = file.length();
+
+        while (file.length() <= originalSize) {
+            Log.i(LOG_TAG, "Waiting for file to be written to...");
+            currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+                    MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be written to.");
+        }
+    }
+
+    /**
+     * Helper to remove all downloads that are registered with the DL Manager.
+     *
+     * Note: This gives us a clean slate b/c it includes downloads that are pending, running,
+     * paused, or have completed.
+     */
+    protected void removeAllCurrentDownloads() {
+        Log.i(LOG_TAG, "Removing all current registered downloads...");
+        Cursor cursor = mDownloadManager.query(new Query());
+        try {
+            if (cursor.moveToFirst()) {
+                do {
+                    int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID);
+                    long downloadId = cursor.getLong(index);
+
+                    mDownloadManager.remove(downloadId);
+                } while (cursor.moveToNext());
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Helper to perform a standard enqueue of data to the mock server.
+     *
+     * @param body The body to return in the response from the server
+     */
+    protected long doStandardEnqueue(byte[] body) throws Exception {
+        // Prepare the mock server with a standard response
+        enqueueResponse(HTTP_OK, body);
+        return doCommonStandardEnqueue();
+    }
+
+    /**
+     * Helper to perform a standard enqueue of data to the mock server.
+     *
+     * @param body The body to return in the response from the server, contained in the file
+     */
+    protected long doStandardEnqueue(File body) throws Exception {
+        // Prepare the mock server with a standard response
+        enqueueResponse(HTTP_OK, body);
+        return doCommonStandardEnqueue();
+    }
+
+    /**
+     * Helper to do the additional steps (setting title and Uri of default filename) when
+     * doing a standard enqueue request to the server.
+     */
+    protected long doCommonStandardEnqueue() throws Exception {
+        Uri uri = getServerUri(DEFAULT_FILENAME);
+        Request request = new Request(uri);
+        request.setTitle(DEFAULT_FILENAME);
+
+        long dlRequest = mDownloadManager.enqueue(request);
+        Log.i(LOG_TAG, "request ID: " + dlRequest);
+        return dlRequest;
+    }
+
+    /**
+     * Helper to verify an int value in a Cursor
+     *
+     * @param cursor The cursor containing the query results
+     * @param columnName The name of the column to query
+     * @param expected The expected int value
+     */
+    protected void verifyInt(Cursor cursor, String columnName, int expected) {
+        int index = cursor.getColumnIndex(columnName);
+        int actual = cursor.getInt(index);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Helper to verify a String value in a Cursor
+     *
+     * @param cursor The cursor containing the query results
+     * @param columnName The name of the column to query
+     * @param expected The expected String value
+     */
+    protected void verifyString(Cursor cursor, String columnName, String expected) {
+        int index = cursor.getColumnIndex(columnName);
+        String actual = cursor.getString(index);
+        Log.i(LOG_TAG, ": " + actual);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Performs a query based on ID and returns a Cursor for the query.
+     *
+     * @param id The id of the download in DL Manager; pass -1 to query all downloads
+     * @return A cursor for the query results
+     */
+    protected Cursor getCursor(long id) throws Exception {
+        Query query = new Query();
+        if (id != -1) {
+            query.setFilterById(id);
+        }
+
+        Cursor cursor = mDownloadManager.query(query);
+        int currentWaitTime = 0;
+
+        try {
+            while (!cursor.moveToFirst()) {
+                Thread.sleep(DEFAULT_WAIT_POLL_TIME);
+                currentWaitTime += DEFAULT_WAIT_POLL_TIME;
+                if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) {
+                    fail("timed out waiting for a non-null query result");
+                }
+                cursor.requery();
+            }
+        } catch (Exception e) {
+            cursor.close();
+            throw e;
+        }
+        return cursor;
+    }
+
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java
new file mode 100644
index 0000000..be3cbf7
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.DownloadManagerBaseTest.DataType;
+import android.net.DownloadManagerBaseTest.MultipleDownloadsCompletedReceiver;
+import android.net.wifi.WifiManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Random;
+
+import junit.framework.AssertionFailedError;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+
+/**
+ * Integration tests of the DownloadManager API.
+ */
+public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
+
+    private static String LOG_TAG = "android.net.DownloadManagerIntegrationTest";
+    private static String PROHIBITED_DIRECTORY = "/system";
+    protected MultipleDownloadsCompletedReceiver mReceiver = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        setWiFiStateOn(true);
+        mServer.play();
+        removeAllCurrentDownloads();
+        mReceiver = registerNewMultipleDownloadsReceiver();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        setWiFiStateOn(true);
+
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+            mReceiver = null;
+            removeAllCurrentDownloads();
+        }
+    }
+
+    /**
+     * Helper that does the actual basic download verification.
+     */
+    protected void doBasicDownload(byte[] blobData) throws Exception {
+        long dlRequest = doStandardEnqueue(blobData);
+
+        // wait for the download to complete
+        waitForDownloadOrTimeout(dlRequest);
+
+        verifyAndCleanupSingleFileDownload(dlRequest, blobData);
+        assertEquals(1, mReceiver.numDownloadsCompleted());
+    }
+
+    /**
+     * Test a basic download of a binary file 500k in size.
+     */
+    @LargeTest
+    public void testBasicBinaryDownload() throws Exception {
+        int fileSize = 500 * 1024;  // 500k
+        byte[] blobData = generateData(fileSize, DataType.BINARY);
+
+        doBasicDownload(blobData);
+    }
+
+    /**
+     * Tests the basic downloading of a text file 300000 bytes in size.
+     */
+    @LargeTest
+    public void testBasicTextDownload() throws Exception {
+        int fileSize = 300000;
+        byte[] blobData = generateData(fileSize, DataType.TEXT);
+
+        doBasicDownload(blobData);
+    }
+
+    /**
+     * Tests when the server drops the connection after all headers (but before any data send).
+     */
+    @LargeTest
+    public void testDropConnection_headers() throws Exception {
+        byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+        MockResponse response = enqueueResponse(HTTP_OK, blobData);
+        response.setCloseConnectionAfterHeader("content-length");
+        long dlRequest = doCommonStandardEnqueue();
+
+        // Download will never complete when header is dropped
+        boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest, DEFAULT_WAIT_POLL_TIME,
+                DEFAULT_MAX_WAIT_TIME);
+
+        assertFalse(success);
+    }
+
+    /**
+     * Tests that we get an error code when the server drops the connection during a download.
+     */
+    @LargeTest
+    public void testServerDropConnection_body() throws Exception {
+        byte[] blobData = generateData(25000, DataType.TEXT);  // file size = 25000 bytes
+
+        MockResponse response = enqueueResponse(HTTP_OK, blobData);
+        response.setCloseConnectionAfterXBytes(15382);
+        long dlRequest = doCommonStandardEnqueue();
+        waitForDownloadOrTimeout(dlRequest);
+
+        Cursor cursor = getCursor(dlRequest);
+        try {
+            verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                    DownloadManager.ERROR_CANNOT_RESUME);
+        } finally {
+            cursor.close();
+        }
+        // Even tho the server drops the connection, we should still get a completed notification
+        assertEquals(1, mReceiver.numDownloadsCompleted());
+    }
+
+    /**
+     * Attempts to download several files simultaneously
+     */
+    @LargeTest
+    public void testMultipleDownloads() throws Exception {
+        // need to be sure all current downloads have stopped first
+        removeAllCurrentDownloads();
+        int NUM_FILES = 50;
+        int MAX_FILE_SIZE = 500 * 1024; // 500 kb
+
+        Random r = new LoggingRng();
+        for (int i=0; i<NUM_FILES; ++i) {
+            int size = r.nextInt(MAX_FILE_SIZE);
+            byte[] blobData = generateData(size, DataType.TEXT);
+
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+            request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i));
+
+            // Prepare the mock server with a standard response
+            enqueueResponse(HTTP_OK, blobData);
+
+            Log.i(LOG_TAG, "request: " + i);
+            mDownloadManager.enqueue(request);
+        }
+
+        waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+        Cursor cursor = mDownloadManager.query(new Query());
+        try {
+            assertEquals(NUM_FILES, cursor.getCount());
+
+            if (cursor.moveToFirst()) {
+                do {
+                    int status = cursor.getInt(cursor.getColumnIndex(
+                            DownloadManager.COLUMN_STATUS));
+                    String filename = cursor.getString(cursor.getColumnIndex(
+                            DownloadManager.COLUMN_URI));
+                    String errorString = String.format(
+                            "File %s failed to download successfully. Status code: %d",
+                            filename, status);
+                    assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status);
+                } while (cursor.moveToNext());
+            }
+
+            assertEquals(NUM_FILES, mReceiver.numDownloadsCompleted());
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Tests trying to download to SD card when the file with same name already exists.
+     */
+    @LargeTest
+    public void testDownloadToExternal_fileExists() throws Exception {
+        File existentFile = createFileOnSD(null, 1, DataType.TEXT, null);
+        byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+        // Prepare the mock server with a standard response
+        enqueueResponse(HTTP_OK, blobData);
+
+        try {
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+
+            Uri localUri = Uri.fromFile(existentFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            long dlRequest = mDownloadManager.enqueue(request);
+
+            // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+            Cursor cursor = getCursor(dlRequest);
+
+            try {
+                verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+                verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                        DownloadManager.ERROR_FILE_ERROR);
+            } finally {
+                cursor.close();
+            }
+        } finally {
+            existentFile.delete();
+        }
+    }
+
+    /**
+     * Tests trying to download a file to SD card.
+     */
+    @LargeTest
+    public void testDownloadToExternal() throws Exception {
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, DEFAULT_FILENAME);
+        // make sure the file doesn't already exist in the directory
+        downloadedFile.delete();
+
+        try {
+            byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+            // Prepare the mock server with a standard response
+            enqueueResponse(HTTP_OK, blobData);
+
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            long dlRequest = mDownloadManager.enqueue(request);
+
+            // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+
+            verifyAndCleanupSingleFileDownload(dlRequest, blobData);
+
+            assertEquals(1, mReceiver.numDownloadsCompleted());
+        } finally {
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests trying to download a file to the system partition.
+     */
+    @LargeTest
+    public void testDownloadToProhibitedDirectory() throws Exception {
+        File downloadedFile = new File(PROHIBITED_DIRECTORY, DEFAULT_FILENAME);
+        try {
+            byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+            // Prepare the mock server with a standard response
+            enqueueResponse(HTTP_OK, blobData);
+
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            try {
+                mDownloadManager.enqueue(request);
+                fail("Failed to throw SecurityException when trying to write to /system.");
+            } catch (SecurityException s) {
+                assertFalse(downloadedFile.exists());
+            }
+        } finally {
+            // Just in case file somehow got created, make sure to delete it
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests that a download set for Wifi does not progress while Wifi is disabled, but resumes
+     * once Wifi is re-enabled.
+     */
+    @LargeTest
+    public void testDownloadNoWifi() throws Exception {
+        long timeout = 60 * 1000; // wait only 60 seconds before giving up
+        int fileSize = 140 * 1024;  // 140k
+        byte[] blobData = generateData(fileSize, DataType.TEXT);
+
+        setWiFiStateOn(false);
+        enqueueResponse(HTTP_OK, blobData);
+
+        try {
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+            request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+            long dlRequest = mDownloadManager.enqueue(request);
+
+            // wait for the download to complete
+            boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest,
+                    WAIT_FOR_DOWNLOAD_POLL_TIME, timeout);
+            assertFalse("Download proceeded without Wifi connection!", success);
+
+            setWiFiStateOn(true);
+            waitForDownloadOrTimeout(dlRequest);
+
+            assertEquals(1, mReceiver.numDownloadsCompleted());
+        } finally {
+            setWiFiStateOn(true);
+        }
+    }
+
+    /**
+     * Tests trying to download two large files (50M bytes, followed by 60M bytes)
+     */
+    @LargeTest
+    public void testInsufficientSpaceSingleFiles() throws Exception {
+        long fileSize1 = 50000000L;
+        long fileSize2 = 60000000L;
+        File largeFile1 = createFileOnSD(null, fileSize1, DataType.TEXT, null);
+        File largeFile2 = createFileOnSD(null, fileSize2, DataType.TEXT, null);
+
+        try {
+            long dlRequest = doStandardEnqueue(largeFile1);
+            waitForDownloadOrTimeout(dlRequest);
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileContents(pfd, largeFile1);
+            verifyFileSize(pfd, largeFile1.length());
+
+            dlRequest = doStandardEnqueue(largeFile2);
+            waitForDownloadOrTimeout(dlRequest);
+            Cursor cursor = getCursor(dlRequest);
+            try {
+                verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                        DownloadManager.ERROR_INSUFFICIENT_SPACE);
+            } finally {
+                cursor.close();
+            }
+        } finally {
+            largeFile1.delete();
+            largeFile2.delete();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/net/DownloadManagerStressTest.java b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java
new file mode 100644
index 0000000..9fa8620
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import java.io.File;
+import java.util.Random;
+
+import android.database.Cursor;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.os.ParcelFileDescriptor;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+
+public class DownloadManagerStressTest extends DownloadManagerBaseTest {
+    private static String LOG_TAG = "android.net.DownloadManagerStressTest";
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mServer.play(0);
+        removeAllCurrentDownloads();
+    }
+
+    /**
+     * Attempts to downloading thousands of files simultaneously
+     */
+    public void testDownloadThousands() throws Exception {
+        int NUM_FILES = 1500;
+        int MAX_FILE_SIZE = 3000;
+        long[] reqs = new long[NUM_FILES];
+
+        // need to be sure all current downloads have stopped first
+        MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver();
+        Cursor cursor = null;
+        try {
+            Random r = new LoggingRng();
+            for (int i = 0; i < NUM_FILES; ++i) {
+                int size = r.nextInt(MAX_FILE_SIZE);
+                byte[] blobData = generateData(size, DataType.TEXT);
+
+                Uri uri = getServerUri(DEFAULT_FILENAME);
+                Request request = new Request(uri);
+                request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i));
+
+                // Prepare the mock server with a standard response
+                enqueueResponse(HTTP_OK, blobData);
+
+                Log.i(LOG_TAG, "issuing request: " + i);
+                long reqId = mDownloadManager.enqueue(request);
+                reqs[i] = reqId;
+            }
+
+            // wait for the download to complete or timeout
+            waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+            cursor = mDownloadManager.query(new Query());
+            assertEquals(NUM_FILES, cursor.getCount());
+            Log.i(LOG_TAG, "Verified number of downloads in download manager is what we expect.");
+            while (cursor.moveToNext()) {
+                int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
+                String filename = cursor.getString(cursor.getColumnIndex(
+                        DownloadManager.COLUMN_URI));
+                String errorString = String.format("File %s failed to download successfully. " +
+                        "Status code: %d", filename, status);
+                assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status);
+            }
+            Log.i(LOG_TAG, "Verified each download was successful.");
+            assertEquals(NUM_FILES, receiver.numDownloadsCompleted());
+            Log.i(LOG_TAG, "Verified number of completed downloads in our receiver.");
+
+            // Verify that for each request, we can open the downloaded file
+            for (int i = 0; i < NUM_FILES; ++i) {
+                ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(reqs[i]);
+                pfd.close();
+            }
+            Log.i(LOG_TAG, "Verified we can open each file.");
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+            mContext.unregisterReceiver(receiver);
+            removeAllCurrentDownloads();
+        }
+    }
+
+    /**
+     * Tests trying to download a large file (50M bytes).
+     */
+    public void testDownloadLargeFile() throws Exception {
+        long fileSize = 50000000L;  // note: kept relatively small to not exceed /cache dir size
+        File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null);
+        MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver();
+
+        try {
+            long dlRequest = doStandardEnqueue(largeFile);
+
+             // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileContents(pfd, largeFile);
+            verifyFileSize(pfd, largeFile.length());
+
+            assertEquals(1, receiver.numDownloadsCompleted());
+            mContext.unregisterReceiver(receiver);
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            largeFile.delete();
+        }
+    }
+
+    /**
+     * Tests trying to download a large file (~300M bytes) when there's not enough space in cache
+     */
+    public void testInsufficientSpace() throws Exception {
+        long fileSize = 300000000L;
+        File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null);
+
+        Cursor cursor = null;
+        try {
+            long dlRequest = doStandardEnqueue(largeFile);
+
+             // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+
+            cursor = getCursor(dlRequest);
+            verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                    DownloadManager.ERROR_INSUFFICIENT_SPACE);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+            largeFile.delete();
+        }
+    }
+}
diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
index 38191b0..c5ea9c1 100644
--- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
@@ -37,7 +37,9 @@
 import java.io.StringReader;
 import java.lang.Runtime;
 import java.lang.Process;
+import java.util.Hashtable;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -117,18 +119,38 @@
 
     /**
      * Helper method to run tests and return the listener that collected the results.
+     *
+     * For the optional params, pass null to use the default values.
+
      * @param pkgName Android application package for tests
-     * @return the {@link CollectingTestRunListener}
+     * @param className (optional) The class containing the method to test
+     * @param methodName (optional) The method in the class of which to test
+     * @param runnerName (optional) The name of the TestRunner of the test on the device to be run
+     * @param params (optional) Any additional parameters to pass into the Test Runner
      * @throws TimeoutException in case of a timeout on the connection.
      * @throws AdbCommandRejectedException if adb rejects the command
      * @throws ShellCommandUnresponsiveException if the device did not output anything for
      * a period longer than the max time to output.
      * @throws IOException if connection to device was lost.
+     * @return the {@link CollectingTestRunListener}
      */
-    private CollectingTestRunListener doRunTests(String pkgName) throws IOException,
-    TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, mDevice);
+    private CollectingTestRunListener doRunTests(String pkgName, String className,
+            String methodName, String runnerName, Map<String, String> params) throws IOException,
+            TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, runnerName,
+                mDevice);
+
+        if (className != null && methodName != null) {
+            testRunner.setMethodName(className, methodName);
+        }
+
+        // Add in any additional args to pass into the test
+        if (params != null) {
+            for (Entry<String, String> argPair : params.entrySet()) {
+                testRunner.addInstrumentationArg(argPair.getKey(), argPair.getValue());
+            }
+        }
+
         CollectingTestRunListener listener = new CollectingTestRunListener();
         try {
             testRunner.run(listener);
@@ -142,16 +164,34 @@
      * Runs the specified packages tests, and returns whether all tests passed or not.
      *
      * @param pkgName Android application package for tests
-     * @return true if every test passed, false otherwise.
+     * @param className The class containing the method to test
+     * @param methodName The method in the class of which to test
+     * @param runnerName The name of the TestRunner of the test on the device to be run
+     * @param params Any additional parameters to pass into the Test Runner
+     * @return true if test passed, false otherwise.
+     */
+    public boolean runDeviceTestsDidAllTestsPass(String pkgName, String className,
+            String methodName, String runnerName, Map<String, String> params) throws IOException,
+            TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
+        CollectingTestRunListener listener = doRunTests(pkgName, className, methodName,
+                runnerName, params);
+        return listener.didAllTestsPass();
+    }
+
+    /**
+     * Runs the specified packages tests, and returns whether all tests passed or not.
+     *
+     * @param pkgName Android application package for tests
      * @throws TimeoutException in case of a timeout on the connection.
      * @throws AdbCommandRejectedException if adb rejects the command
      * @throws ShellCommandUnresponsiveException if the device did not output anything for
      * a period longer than the max time to output.
      * @throws IOException if connection to device was lost.
+     * @return true if every test passed, false otherwise.
      */
     public boolean runDeviceTestsDidAllTestsPass(String pkgName) throws IOException,
             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
-        CollectingTestRunListener listener = doRunTests(pkgName);
+        CollectingTestRunListener listener = doRunTests(pkgName, null, null, null, null);
         return listener.didAllTestsPass();
     }
 
@@ -535,7 +575,7 @@
     }
 
     // For collecting results from running device tests
-    private static class CollectingTestRunListener implements ITestRunListener {
+    public static class CollectingTestRunListener implements ITestRunListener {
 
         private boolean mAllTestsPassed = true;
         private String mTestRunErrorMessage = null;
diff --git a/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java
new file mode 100644
index 0000000..cfabb6c
--- /dev/null
+++ b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.pm.PackageManagerHostTestUtils;
+
+import com.android.ddmlib.Log;
+import com.android.hosttest.DeviceTestCase;
+import com.android.hosttest.DeviceTestSuite;
+
+import java.io.File;
+import java.util.Hashtable;
+
+import junit.framework.Test;
+
+/**
+ * Host-based tests of the DownloadManager API. (Uses a device-based app to actually invoke the
+ * various tests.)
+ */
+public class DownloadManagerHostTests extends DeviceTestCase {
+    protected PackageManagerHostTestUtils mPMUtils = null;
+
+    private static final String LOG_TAG = "android.net.DownloadManagerHostTests";
+    private static final String FILE_DOWNLOAD_APK = "DownloadManagerTestApp.apk";
+    private static final String FILE_DOWNLOAD_PKG = "com.android.frameworks.downloadmanagertests";
+    private static final String FILE_DOWNLOAD_CLASS =
+            "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp";
+    private static final String DOWNLOAD_TEST_RUNNER_NAME =
+            "com.android.frameworks.downloadmanagertests.DownloadManagerTestRunner";
+
+    // Extra parameters to pass to the TestRunner
+    private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri";
+    // Note: External environment variable ANDROID_TEST_EXTERNAL_URI must be set to point to the
+    // external URI under which the files downloaded by the tests can be found. Note that the Uri
+    // must be accessible by the device during a test run.
+    private static String EXTERNAL_DOWNLOAD_URI_VALUE = null;
+
+    Hashtable<String, String> mExtraParams = null;
+
+    public static Test suite() {
+        return new DeviceTestSuite(DownloadManagerHostTests.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // ensure apk path has been set before test is run
+        assertNotNull(getTestAppPath());
+        mPMUtils = new PackageManagerHostTestUtils(getDevice());
+        EXTERNAL_DOWNLOAD_URI_VALUE = System.getenv("ANDROID_TEST_EXTERNAL_URI");
+        assertNotNull(EXTERNAL_DOWNLOAD_URI_VALUE);
+        mExtraParams = getExtraParams();
+    }
+
+    /**
+     * Helper function to get extra params that can be used to pass into the helper app.
+     */
+    protected Hashtable<String, String> getExtraParams() {
+        Hashtable<String, String> extraParams = new Hashtable<String, String>();
+        extraParams.put(EXTERNAL_DOWNLOAD_URI_KEY, EXTERNAL_DOWNLOAD_URI_VALUE);
+        return extraParams;
+    }
+
+    /**
+     * Tests that a large download over WiFi
+     * @throws Exception if the test failed at any point
+     */
+    public void testLargeDownloadOverWiFi() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runLargeDownloadOverWiFi", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+
+        assertTrue("Failed to install large file over WiFi in < 10 minutes!", testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to initiate a download on the device, reboots the device,
+     * then waits and verifies the download succeeded.
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadManagerSingleReboot() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+
+        assertTrue("Failed to initiate download properly!", testPassed);
+        mPMUtils.rebootDevice();
+        testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+        assertTrue("Failed to verify initiated download completed properyly!", testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to initiate a download on the device, reboots the device three
+     * times (using different intervals), then waits and verifies the download succeeded.
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadManagerMultipleReboots() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+
+        assertTrue("Failed to initiate download properly!", testPassed);
+        Thread.sleep(5000);
+
+        // Do 3 random reboots - after 13, 9, and 19 seconds
+        Log.i(LOG_TAG, "First reboot...");
+        mPMUtils.rebootDevice();
+        Thread.sleep(13000);
+        Log.i(LOG_TAG, "Second reboot...");
+        mPMUtils.rebootDevice();
+        Thread.sleep(9000);
+        Log.i(LOG_TAG, "Third reboot...");
+        mPMUtils.rebootDevice();
+        Thread.sleep(19000);
+        testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+        assertTrue("Failed to verify initiated download completed properyly!", testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to test download while WiFi is enabled/disabled multiple times
+     * during the download.
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadMultipleWiFiEnableDisable() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runDownloadMultipleWiFiEnableDisable",
+                DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+        assertTrue(testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to test switching on/off both airplane mode and WiFi
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadMultipleSwitching() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runDownloadMultipleSwitching",
+                DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+        assertTrue(testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to test switching on/off airplane mode multiple times
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadMultipleAirplaneModeEnableDisable() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runDownloadMultipleAirplaneModeEnableDisable",
+                DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+        assertTrue(testPassed);
+    }
+}
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
new file mode 100644
index 0000000..576765c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+				   ../../../coretests/src/android/net/DownloadManagerBaseTest.java
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := DownloadManagerTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..3f2be3c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.frameworks.downloadmanagertests">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+    <application android:label="DownloadManagerTestApp">
+            <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name=".DownloadManagerTestRunner"
+            android:targetPackage="com.android.frameworks.downloadmanagertests"
+            android:label="Frameworks Download Manager Test App" />
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java
new file mode 100644
index 0000000..ef81353
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.frameworks.downloadmanagertests;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.DownloadManager;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.DownloadManagerBaseTest;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+import coretestutils.http.RecordedRequest;
+
+/**
+ * Class to test downloading files from a real (not mock) external server.
+ */
+public class DownloadManagerTestApp extends DownloadManagerBaseTest {
+    protected static String DOWNLOAD_STARTED_FLAG = "DMTEST_DOWNLOAD_STARTED";
+    protected static String LOG_TAG =
+            "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp";
+
+    protected static String DOWNLOAD_500K_FILENAME = "External541kb.apk";
+    protected static long DOWNLOAD_500K_FILESIZE = 570927;
+    protected static String DOWNLOAD_1MB_FILENAME = "External1mb.apk";
+    protected static long DOWNLOAD_1MB_FILESIZE = 1041262;
+    protected static String DOWNLOAD_10MB_FILENAME = "External10mb.apk";
+    protected static long DOWNLOAD_10MB_FILESIZE = 10258741;
+
+    // Values to be obtained from TestRunner
+    private String externalDownloadUriValue = null;
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        DownloadManagerTestRunner mRunner = (DownloadManagerTestRunner)getInstrumentation();
+        externalDownloadUriValue = mRunner.externalDownloadUriValue;
+        assertNotNull(externalDownloadUriValue);
+
+        if (!externalDownloadUriValue.endsWith("/")) {
+            externalDownloadUriValue += "/";
+        }
+    }
+
+    /**
+     * Gets the external URL of the file to download
+     *
+     * @return the Uri of the external file to download
+     */
+    private Uri getExternalFileUri(String file) {
+        return Uri.parse(externalDownloadUriValue + file);
+    }
+
+    /**
+     * Gets the path to the file that flags that a download has started. The file contains the
+     * DownloadManager id of the download being trackted between reboot sessions.
+     *
+     * @return The path of the file tracking that a download has started
+     * @throws InterruptedException if interrupted
+     * @throws Exception if timed out while waiting for SD card to mount
+     */
+    protected String getDownloadStartedFilePath() {
+        String path = Environment.getExternalStorageDirectory().getPath();
+        return path + File.separatorChar + DOWNLOAD_STARTED_FLAG;
+    }
+
+    /**
+     * Common setup steps for downloads.
+     *
+     * Note that these are not included in setUp, so that individual tests can control their own
+     * state between reboots, etc.
+     */
+    protected void doCommonDownloadSetup() throws Exception {
+        setWiFiStateOn(true);
+        setAirplaneModeOn(false);
+        waitForExternalStoreMount();
+        removeAllCurrentDownloads();
+    }
+
+    /**
+     * Initiates a download.
+     *
+     * Queues up a download to the download manager, and saves the DownloadManager's assigned
+     * download ID for this download to a file.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void initiateDownload() throws Exception {
+        String filename = DOWNLOAD_1MB_FILENAME;
+        mContext.deleteFile(DOWNLOAD_STARTED_FLAG);
+        FileOutputStream fileOutput = mContext.openFileOutput(DOWNLOAD_STARTED_FLAG, 0);
+        DataOutputStream outputFile = null;
+        doCommonDownloadSetup();
+
+        try {
+            long dlRequest = -1;
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            assertTrue(dlRequest != -1);
+
+            // Store ID of download for later retrieval
+            outputFile = new DataOutputStream(fileOutput);
+            outputFile.writeLong(dlRequest);
+        } finally {
+            if (outputFile != null) {
+                outputFile.flush();
+                outputFile.close();
+            }
+        }
+    }
+
+    /**
+     * Waits for a previously-initiated download and verifies it has completed successfully.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void verifyFileDownloadSucceeded() throws Exception {
+        String filename = DOWNLOAD_1MB_FILENAME;
+        long filesize = DOWNLOAD_1MB_FILESIZE;
+        long dlRequest = -1;
+        boolean rebootMarkerValid = false;
+        DataInputStream dataInputFile = null;
+
+        setWiFiStateOn(true);
+        setAirplaneModeOn(false);
+
+        try {
+            FileInputStream inFile = mContext.openFileInput(DOWNLOAD_STARTED_FLAG);
+            dataInputFile = new DataInputStream(inFile);
+            dlRequest = dataInputFile.readLong();
+        } catch (Exception e) {
+            // The file was't valid so we just leave the flag false
+            Log.i(LOG_TAG, "Unable to determine initial download id.");
+            throw e;
+        } finally {
+            if (dataInputFile != null) {
+                dataInputFile.close();
+            }
+            mContext.deleteFile(DOWNLOAD_STARTED_FLAG);
+        }
+
+        assertTrue(dlRequest != -1);
+        Cursor cursor = getCursor(dlRequest);
+        ParcelFileDescriptor pfd = null;
+        try {
+            assertTrue("Unable to query last initiated download!", cursor.moveToFirst());
+
+            int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+            int status = cursor.getInt(columnIndex);
+            int currentWaitTime = 0;
+
+            // Wait until the download finishes
+            waitForDownloadOrTimeout(dlRequest);
+
+            Log.i(LOG_TAG, "Verifying download information...");
+            // Verify specific info about the file (size, name, etc)...
+            pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "error: " + e.toString());
+            throw e;
+        } finally {
+            // Clean up...
+            cursor.close();
+            mDownloadManager.remove(dlRequest);
+            if (pfd != null) {
+                pfd.close();
+            }
+        }
+    }
+
+    /**
+     * Tests downloading a large file over WiFi (~10 Mb).
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runLargeDownloadOverWiFi() throws Exception {
+        String filename = DOWNLOAD_10MB_FILENAME;
+        long filesize = DOWNLOAD_10MB_FILESIZE;
+        long dlRequest = -1;
+        doCommonDownloadSetup();
+
+        // Make sure there are no pending downloads currently going on
+        removeAllCurrentDownloads();
+
+        Uri remoteUri = getExternalFileUri(filename);
+        Request request = new Request(remoteUri);
+        request.setMediaType(getMimeMapping(DownloadFileType.APK));
+
+        dlRequest = mDownloadManager.enqueue(request);
+
+        // Rather large file, so wait up to 15 mins...
+        waitForDownloadOrTimeout(dlRequest, WAIT_FOR_DOWNLOAD_POLL_TIME, 15 * 60 * 1000);
+
+        Cursor cursor = getCursor(dlRequest);
+        ParcelFileDescriptor pfd = null;
+        try {
+            Log.i(LOG_TAG, "Verifying download information...");
+            // Verify specific info about the file (size, name, etc)...
+            pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            if (pfd != null) {
+                pfd.close();
+            }
+            mDownloadManager.remove(dlRequest);
+            cursor.close();
+        }
+    }
+
+    /**
+     * Tests that downloads resume when switching back and forth from having connectivity to
+     * having no connectivity using both WiFi and airplane mode.
+     *
+     * Note: Device has no mobile access when running this test.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runDownloadMultipleSwitching() throws Exception {
+        String filename = DOWNLOAD_500K_FILENAME;
+        long filesize = DOWNLOAD_500K_FILESIZE;
+        doCommonDownloadSetup();
+
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, filename);
+
+        long dlRequest = -1;
+        try {
+            downloadedFile.delete();
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            // Local destination of downloaded file
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            request.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            // make sure we're starting to download some data...
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            setWiFiStateOn(false);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning on airplane mode...");
+            setAirplaneModeOn(true);
+            Thread.sleep(30 * 1000);  // wait 30 secs
+
+            // download disable
+            setWiFiStateOn(true);
+            Thread.sleep(30 * 1000);  // wait 30 secs
+
+            // download enable
+            Log.i(LOG_TAG, "Turning off airplane mode...");
+            setAirplaneModeOn(false);
+            Thread.sleep(5 * 1000);  // wait 5 seconds
+
+            // download disable
+            Log.i(LOG_TAG, "Turning off WiFi...");
+            setWiFiStateOn(false);
+            Thread.sleep(30 * 1000);  // wait 30 secs
+
+            // finally, turn WiFi back on and finish up the download
+            Log.i(LOG_TAG, "Turning on WiFi...");
+            setWiFiStateOn(true);
+            Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete...");
+            waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000);
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            Log.i(LOG_TAG, "Cleaning up files...");
+            if (dlRequest != -1) {
+                mDownloadManager.remove(dlRequest);
+            }
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests that downloads resume when switching on/off WiFi at various intervals.
+     *
+     * Note: Device has no mobile access when running this test.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runDownloadMultipleWiFiEnableDisable() throws Exception {
+        String filename = DOWNLOAD_500K_FILENAME;
+        long filesize = DOWNLOAD_500K_FILESIZE;
+        doCommonDownloadSetup();
+
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, filename);
+        long dlRequest = -1;
+        try {
+            downloadedFile.delete();
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            // Local destination of downloaded file
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            // are we making any progress?
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning off WiFi...");
+            setWiFiStateOn(false);
+            Thread.sleep(40 * 1000);  // wait 40 seconds
+
+            // enable download...
+            Log.i(LOG_TAG, "Turning on WiFi again...");
+            setWiFiStateOn(true);
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning off WiFi...");
+            setWiFiStateOn(false);
+            Thread.sleep(20 * 1000);  // wait 20 seconds
+
+            // enable download...
+            Log.i(LOG_TAG, "Turning on WiFi again...");
+            setWiFiStateOn(true);
+
+            Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete...");
+            waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000);
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            Log.i(LOG_TAG, "Cleaning up files...");
+            if (dlRequest != -1) {
+                mDownloadManager.remove(dlRequest);
+            }
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests that downloads resume when switching on/off Airplane mode numerous times at
+     * various intervals.
+     *
+     * Note: Device has no mobile access when running this test.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runDownloadMultipleAirplaneModeEnableDisable() throws Exception {
+        String filename = DOWNLOAD_500K_FILENAME;
+        long filesize = DOWNLOAD_500K_FILESIZE;
+        // make sure WiFi is enabled, and airplane mode is not on
+        doCommonDownloadSetup();
+
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, filename);
+        long dlRequest = -1;
+        try {
+            downloadedFile.delete();
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            // Local destination of downloaded file
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            // are we making any progress?
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning on Airplane mode...");
+            setAirplaneModeOn(true);
+            Thread.sleep(60 * 1000);  // wait 1 minute
+
+            // download enable
+            Log.i(LOG_TAG, "Turning off Airplane mode...");
+            setAirplaneModeOn(false);
+            // make sure we're starting to download some data...
+            waitForFileToGrow(downloadedFile);
+
+            // reenable the connection to start up the download again
+            Log.i(LOG_TAG, "Turning on Airplane mode again...");
+            setAirplaneModeOn(true);
+            Thread.sleep(20 * 1000);  // wait 20 seconds
+
+            // Finish up the download...
+            Log.i(LOG_TAG, "Turning off Airplane mode again...");
+            setAirplaneModeOn(false);
+
+            Log.i(LOG_TAG, "Waiting up to 3 minutes for donwload to complete...");
+            waitForDownloadsOrTimeout(dlRequest, 180 * 1000);  // wait up to 3 mins before timeout
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            Log.i(LOG_TAG, "Cleaning up files...");
+            if (dlRequest != -1) {
+                mDownloadManager.remove(dlRequest);
+            }
+            downloadedFile.delete();
+        }
+    }
+}
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java
new file mode 100644
index 0000000..0f16619
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.downloadmanagertests;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import com.android.frameworks.downloadmanagertests.DownloadManagerTestApp;
+
+import junit.framework.TestSuite;
+
+/**
+ * Instrumentation Test Runner for all download manager tests.
+ *
+ * To run the download manager tests:
+ *
+ * adb shell am instrument -e external_download_1mb_uri <uri> external_download_500k_uri <uri> \
+ *     -w com.android.frameworks.downloadmanagertests/.DownloadManagerTestRunner
+ */
+
+public class DownloadManagerTestRunner extends InstrumentationTestRunner {
+    private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri";
+    public String externalDownloadUriValue = null;
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(DownloadManagerTestApp.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return DownloadManagerTestRunner.class.getClassLoader();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        // Extract the extra params passed in from the bundle...
+        String externalDownloadUri = (String) icicle.get(EXTERNAL_DOWNLOAD_URI_KEY);
+        if (externalDownloadUri != null) {
+            externalDownloadUriValue = externalDownloadUri;
+        }
+        super.onCreate(icicle);
+    }
+
+}
diff --git a/core/tests/utillib/Android.mk b/core/tests/utillib/Android.mk
new file mode 100644
index 0000000..299ea5a
--- /dev/null
+++ b/core/tests/utillib/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_MODULE := frameworks-core-util-lib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/utillib/src/coretestutils/http/MockResponse.java b/core/tests/utillib/src/coretestutils/http/MockResponse.java
new file mode 100644
index 0000000..5b03e5f
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockResponse.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package coretestutils.http;
+
+import static coretestutils.http.MockWebServer.ASCII;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.util.Log;
+
+/**
+ * A scripted response to be replayed by the mock web server.
+ */
+public class MockResponse {
+    private static final byte[] EMPTY_BODY = new byte[0];
+    static final String LOG_TAG = "coretestutils.http.MockResponse";
+
+    private String status = "HTTP/1.1 200 OK";
+    private Map<String, String> headers = new HashMap<String, String>();
+    private byte[] body = EMPTY_BODY;
+    private boolean closeConnectionAfter = false;
+    private String closeConnectionAfterHeader = null;
+    private int closeConnectionAfterXBytes = -1;
+    private int pauseConnectionAfterXBytes = -1;
+    private File bodyExternalFile = null;
+
+    public MockResponse() {
+        addHeader("Content-Length", 0);
+    }
+
+    /**
+     * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
+     */
+    public String getStatus() {
+        return status;
+    }
+
+    public MockResponse setResponseCode(int code) {
+        this.status = "HTTP/1.1 " + code + " OK";
+        return this;
+    }
+
+    /**
+     * Returns the HTTP headers, such as "Content-Length: 0".
+     */
+    public List<String> getHeaders() {
+        List<String> headerStrings = new ArrayList<String>();
+        for (String header : headers.keySet()) {
+            headerStrings.add(header + ": " + headers.get(header));
+        }
+        return headerStrings;
+    }
+
+    public MockResponse addHeader(String header, String value) {
+        headers.put(header.toLowerCase(), value);
+        return this;
+    }
+
+    public MockResponse addHeader(String header, long value) {
+        return addHeader(header, Long.toString(value));
+    }
+
+    public MockResponse removeHeader(String header) {
+        headers.remove(header.toLowerCase());
+        return this;
+    }
+
+    /**
+     * Returns true if the body should come from an external file, false otherwise.
+     */
+    private boolean bodyIsExternal() {
+        return bodyExternalFile != null;
+    }
+
+    /**
+     * Returns an input stream containing the raw HTTP payload.
+     */
+    public InputStream getBody() {
+        if (bodyIsExternal()) {
+            try {
+                return new FileInputStream(bodyExternalFile);
+            } catch (FileNotFoundException e) {
+                Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath());
+            }
+        }
+        return new ByteArrayInputStream(this.body);
+    }
+
+    public MockResponse setBody(File body) {
+        addHeader("Content-Length", body.length());
+        this.bodyExternalFile = body;
+        return this;
+    }
+
+    public MockResponse setBody(byte[] body) {
+        addHeader("Content-Length", body.length);
+        this.body = body;
+        return this;
+    }
+
+    public MockResponse setBody(String body) {
+        try {
+            return setBody(body.getBytes(ASCII));
+        } catch (UnsupportedEncodingException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Sets the body as chunked.
+     *
+     * Currently chunked body is not supported for external files as bodies.
+     */
+    public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException {
+        addHeader("Transfer-encoding", "chunked");
+
+        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+        int pos = 0;
+        while (pos < body.length) {
+            int chunkSize = Math.min(body.length - pos, maxChunkSize);
+            bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII));
+            bytesOut.write("\r\n".getBytes(ASCII));
+            bytesOut.write(body, pos, chunkSize);
+            bytesOut.write("\r\n".getBytes(ASCII));
+            pos += chunkSize;
+        }
+        bytesOut.write("0\r\n".getBytes(ASCII));
+        this.body = bytesOut.toByteArray();
+        return this;
+    }
+
+    public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException {
+        return setChunkedBody(body.getBytes(ASCII), maxChunkSize);
+    }
+
+    @Override public String toString() {
+        return status;
+    }
+
+    public boolean shouldCloseConnectionAfter() {
+        return closeConnectionAfter;
+    }
+
+    public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) {
+        this.closeConnectionAfter = closeConnectionAfter;
+        return this;
+    }
+
+    /**
+     * Sets the header after which sending the server should close the connection.
+     */
+    public MockResponse setCloseConnectionAfterHeader(String header) {
+        closeConnectionAfterHeader = header;
+        setCloseConnectionAfter(true);
+        return this;
+    }
+
+    /**
+     * Returns the header after which sending the server should close the connection.
+     */
+    public String getCloseConnectionAfterHeader() {
+        return closeConnectionAfterHeader;
+    }
+
+    /**
+     * Sets the number of bytes in the body to send before which the server should close the
+     * connection. Set to -1 to unset and send the entire body (default).
+     */
+    public MockResponse setCloseConnectionAfterXBytes(int position) {
+        closeConnectionAfterXBytes = position;
+        setCloseConnectionAfter(true);
+        return this;
+    }
+
+    /**
+     * Returns the number of bytes in the body to send before which the server should close the
+     * connection. Returns -1 if the entire body should be sent (default).
+     */
+    public int getCloseConnectionAfterXBytes() {
+        return closeConnectionAfterXBytes;
+    }
+
+    /**
+     * Sets the number of bytes in the body to send before which the server should pause the
+     * connection (stalls in sending data). Only one pause per response is supported.
+     * Set to -1 to unset pausing (default).
+     */
+    public MockResponse setPauseConnectionAfterXBytes(int position) {
+        pauseConnectionAfterXBytes = position;
+        return this;
+    }
+
+    /**
+     * Returns the number of bytes in the body to send before which the server should pause the
+     * connection (stalls in sending data). (Returns -1 if it should not pause).
+     */
+    public int getPauseConnectionAfterXBytes() {
+        return pauseConnectionAfterXBytes;
+    }
+
+    /**
+     * Returns true if this response is flagged to pause the connection mid-stream, false otherwise
+     */
+    public boolean getShouldPause() {
+        return (pauseConnectionAfterXBytes != -1);
+    }
+
+    /**
+     * Returns true if this response is flagged to close the connection mid-stream, false otherwise
+     */
+    public boolean getShouldClose() {
+        return (closeConnectionAfterXBytes != -1);
+    }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/MockWebServer.java b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
new file mode 100644
index 0000000..c329ffa
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package coretestutils.http;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import android.util.Log;
+
+/**
+ * A scriptable web server. Callers supply canned responses and the server
+ * replays them upon request in sequence.
+ *
+ * TODO: merge with the version from libcore/support/src/tests/java once it's in.
+ */
+public final class MockWebServer {
+    static final String ASCII = "US-ASCII";
+    static final String LOG_TAG = "coretestutils.http.MockWebServer";
+
+    private final BlockingQueue<RecordedRequest> requestQueue
+            = new LinkedBlockingQueue<RecordedRequest>();
+    private final BlockingQueue<MockResponse> responseQueue
+            = new LinkedBlockingQueue<MockResponse>();
+    private int bodyLimit = Integer.MAX_VALUE;
+    private final ExecutorService executor = Executors.newCachedThreadPool();
+    // keep Futures around so we can rethrow any exceptions thrown by Callables
+    private final Queue<Future<?>> futures = new LinkedList<Future<?>>();
+    private final Object downloadPauseLock = new Object();
+    // global flag to signal when downloads should resume on the server
+    private volatile boolean downloadResume = false;
+
+    private int port = -1;
+
+    public int getPort() {
+        if (port == -1) {
+            throw new IllegalStateException("Cannot retrieve port before calling play()");
+        }
+        return port;
+    }
+
+    /**
+     * Returns a URL for connecting to this server.
+     *
+     * @param path the request path, such as "/".
+     */
+    public URL getUrl(String path) throws MalformedURLException {
+        return new URL("http://localhost:" + getPort() + path);
+    }
+
+    /**
+     * Sets the number of bytes of the POST body to keep in memory to the given
+     * limit.
+     */
+    public void setBodyLimit(int maxBodyLength) {
+        this.bodyLimit = maxBodyLength;
+    }
+
+    public void enqueue(MockResponse response) {
+        responseQueue.add(response);
+    }
+
+    /**
+     * Awaits the next HTTP request, removes it, and returns it. Callers should
+     * use this to verify the request sent was as intended.
+     */
+    public RecordedRequest takeRequest() throws InterruptedException {
+        return requestQueue.take();
+    }
+
+    public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException {
+        return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+    }
+
+    public List<RecordedRequest> drainRequests() {
+        List<RecordedRequest> requests = new ArrayList<RecordedRequest>();
+        requestQueue.drainTo(requests);
+        return requests;
+    }
+
+    /**
+     * Starts the server, serves all enqueued requests, and shuts the server
+     * down using the default (server-assigned) port.
+     */
+    public void play() throws IOException {
+        play(0);
+    }
+
+    /**
+     * Starts the server, serves all enqueued requests, and shuts the server
+     * down.
+     *
+     * @param port The port number to use to listen to connections on; pass in 0 to have the
+     * server automatically assign a free port
+     */
+    public void play(int portNumber) throws IOException {
+        final ServerSocket ss = new ServerSocket(portNumber);
+        ss.setReuseAddress(true);
+        port = ss.getLocalPort();
+        submitCallable(new Callable<Void>() {
+            public Void call() throws Exception {
+                int count = 0;
+                while (true) {
+                    if (count > 0 && responseQueue.isEmpty()) {
+                        ss.close();
+                        executor.shutdown();
+                        return null;
+                    }
+
+                    serveConnection(ss.accept());
+                    count++;
+                }
+            }
+        });
+    }
+
+    private void serveConnection(final Socket s) {
+        submitCallable(new Callable<Void>() {
+            public Void call() throws Exception {
+                InputStream in = new BufferedInputStream(s.getInputStream());
+                OutputStream out = new BufferedOutputStream(s.getOutputStream());
+
+                int sequenceNumber = 0;
+                while (true) {
+                    RecordedRequest request = readRequest(in, sequenceNumber);
+                    if (request == null) {
+                        if (sequenceNumber == 0) {
+                            throw new IllegalStateException("Connection without any request!");
+                        } else {
+                            break;
+                        }
+                    }
+                    requestQueue.add(request);
+                    MockResponse response = computeResponse(request);
+                    writeResponse(out, response);
+                    if (response.shouldCloseConnectionAfter()) {
+                        break;
+                    }
+                    sequenceNumber++;
+                }
+
+                in.close();
+                out.close();
+                return null;
+            }
+        });
+    }
+
+    private void submitCallable(Callable<?> callable) {
+        Future<?> future = executor.submit(callable);
+        futures.add(future);
+    }
+
+    /**
+     * Check for and raise any exceptions that have been thrown by child threads.  Will not block on
+     * children still running.
+     * @throws ExecutionException for the first child thread that threw an exception
+     */
+    public void checkForExceptions() throws ExecutionException, InterruptedException {
+        final int originalSize = futures.size();
+        for (int i = 0; i < originalSize; i++) {
+            Future<?> future = futures.remove();
+            try {
+                future.get(0, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                futures.add(future); // still running
+            }
+        }
+    }
+
+    /**
+     * @param sequenceNumber the index of this request on this connection.
+     */
+    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
+        String request = readAsciiUntilCrlf(in);
+        if (request.equals("")) {
+            return null; // end of data; no more requests
+        }
+
+        List<String> headers = new ArrayList<String>();
+        int contentLength = -1;
+        boolean chunked = false;
+        String header;
+        while (!(header = readAsciiUntilCrlf(in)).equals("")) {
+            headers.add(header);
+            String lowercaseHeader = header.toLowerCase();
+            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
+                contentLength = Integer.parseInt(header.substring(15).trim());
+            }
+            if (lowercaseHeader.startsWith("transfer-encoding:") &&
+                    lowercaseHeader.substring(18).trim().equals("chunked")) {
+                chunked = true;
+            }
+        }
+
+        boolean hasBody = false;
+        TruncatingOutputStream requestBody = new TruncatingOutputStream();
+        List<Integer> chunkSizes = new ArrayList<Integer>();
+        if (contentLength != -1) {
+            hasBody = true;
+            transfer(contentLength, in, requestBody);
+        } else if (chunked) {
+            hasBody = true;
+            while (true) {
+                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
+                if (chunkSize == 0) {
+                    readEmptyLine(in);
+                    break;
+                }
+                chunkSizes.add(chunkSize);
+                transfer(chunkSize, in, requestBody);
+                readEmptyLine(in);
+            }
+        }
+
+        if (request.startsWith("GET ")) {
+            if (hasBody) {
+                throw new IllegalArgumentException("GET requests should not have a body!");
+            }
+        } else if (request.startsWith("POST ")) {
+            if (!hasBody) {
+                throw new IllegalArgumentException("POST requests must have a body!");
+            }
+        } else {
+            throw new UnsupportedOperationException("Unexpected method: " + request);
+        }
+
+        return new RecordedRequest(request, headers, chunkSizes,
+                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
+    }
+
+    /**
+     * Returns a response to satisfy {@code request}.
+     */
+    private MockResponse computeResponse(RecordedRequest request) throws InterruptedException {
+        if (responseQueue.isEmpty()) {
+            throw new IllegalStateException("Unexpected request: " + request);
+        }
+        return responseQueue.take();
+    }
+
+    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
+        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
+        boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null);
+
+        // Send headers
+        String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader();
+        for (String header : response.getHeaders()) {
+            out.write((header + "\r\n").getBytes(ASCII));
+
+            if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) {
+                Log.i(LOG_TAG, "Closing connection after header" + header);
+                break;
+            }
+        }
+
+        // Send actual body data
+        if (!doCloseConnectionAfterHeader) {
+            out.write(("\r\n").getBytes(ASCII));
+
+            InputStream body = response.getBody();
+            final int READ_BLOCK_SIZE = 10000;  // process blocks this size
+            byte[] currentBlock = new byte[READ_BLOCK_SIZE];
+            int currentBlockSize = 0;
+            int writtenSoFar = 0;
+
+            boolean shouldPause = response.getShouldPause();
+            boolean shouldClose = response.getShouldClose();
+            int pause = response.getPauseConnectionAfterXBytes();
+            int close = response.getCloseConnectionAfterXBytes();
+
+            // Don't bother pausing if it's set to pause -after- the connection should be dropped
+            if (shouldPause && shouldClose && (pause > close)) {
+                shouldPause = false;
+            }
+
+            // Process each block we read in...
+            while ((currentBlockSize = body.read(currentBlock)) != -1) {
+                int startIndex = 0;
+                int writeLength = currentBlockSize;
+
+                // handle the case of pausing
+                if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) {
+                    writeLength = pause - writtenSoFar;
+                    out.write(currentBlock, 0, writeLength);
+                    out.flush();
+                    writtenSoFar += writeLength;
+
+                    // now pause...
+                    try {
+                        Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes");
+                        // Wait until someone tells us to resume sending...
+                        synchronized(downloadPauseLock) {
+                            while (!downloadResume) {
+                                downloadPauseLock.wait();
+                            }
+                            // reset resume back to false
+                            downloadResume = false;
+                        }
+                    } catch (InterruptedException e) {
+                        Log.e(LOG_TAG, "Server was interrupted during pause in download.");
+                    }
+
+                    startIndex = writeLength;
+                    writeLength = currentBlockSize - writeLength;
+                }
+
+                // handle the case of closing the connection
+                if (shouldClose && (writtenSoFar + writeLength > close)) {
+                    writeLength = close - writtenSoFar;
+                    out.write(currentBlock, startIndex, writeLength);
+                    writtenSoFar += writeLength;
+                    Log.i(LOG_TAG, "Closing connection after " + close + " bytes");
+                    break;
+                }
+                out.write(currentBlock, startIndex, writeLength);
+                writtenSoFar += writeLength;
+            }
+        }
+        out.flush();
+    }
+
+    /**
+     * Transfer bytes from {@code in} to {@code out} until either {@code length}
+     * bytes have been transferred or {@code in} is exhausted.
+     */
+    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[1024];
+        while (length > 0) {
+            int count = in.read(buffer, 0, Math.min(buffer.length, length));
+            if (count == -1) {
+                return;
+            }
+            out.write(buffer, 0, count);
+            length -= count;
+        }
+    }
+
+    /**
+     * Returns the text from {@code in} until the next "\r\n", or null if
+     * {@code in} is exhausted.
+     */
+    private String readAsciiUntilCrlf(InputStream in) throws IOException {
+        StringBuilder builder = new StringBuilder();
+        while (true) {
+            int c = in.read();
+            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
+                builder.deleteCharAt(builder.length() - 1);
+                return builder.toString();
+            } else if (c == -1) {
+                return builder.toString();
+            } else {
+                builder.append((char) c);
+            }
+        }
+    }
+
+    private void readEmptyLine(InputStream in) throws IOException {
+        String line = readAsciiUntilCrlf(in);
+        if (!line.equals("")) {
+            throw new IllegalStateException("Expected empty but was: " + line);
+        }
+    }
+
+    /**
+     * An output stream that drops data after bodyLimit bytes.
+     */
+    private class TruncatingOutputStream extends ByteArrayOutputStream {
+        private int numBytesReceived = 0;
+        @Override public void write(byte[] buffer, int offset, int len) {
+            numBytesReceived += len;
+            super.write(buffer, offset, Math.min(len, bodyLimit - count));
+        }
+        @Override public void write(int oneByte) {
+            numBytesReceived++;
+            if (count < bodyLimit) {
+                super.write(oneByte);
+            }
+        }
+    }
+
+    /**
+     * Trigger the server to resume sending the download
+     */
+    public void doResumeDownload() {
+        synchronized (downloadPauseLock) {
+            downloadResume = true;
+            downloadPauseLock.notifyAll();
+        }
+    }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
new file mode 100644
index 0000000..293ff80
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package coretestutils.http;
+
+import java.util.List;
+
+/**
+ * An HTTP request that came into the mock web server.
+ */
+public final class RecordedRequest {
+    private final String requestLine;
+    private final List<String> headers;
+    private final List<Integer> chunkSizes;
+    private final int bodySize;
+    private final byte[] body;
+    private final int sequenceNumber;
+
+    RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
+            int bodySize, byte[] body, int sequenceNumber) {
+        this.requestLine = requestLine;
+        this.headers = headers;
+        this.chunkSizes = chunkSizes;
+        this.bodySize = bodySize;
+        this.body = body;
+        this.sequenceNumber = sequenceNumber;
+    }
+
+    public String getRequestLine() {
+        return requestLine;
+    }
+
+    public List<String> getHeaders() {
+        return headers;
+    }
+
+    /**
+     * Returns the sizes of the chunks of this request's body, or an empty list
+     * if the request's body was empty or unchunked.
+     */
+    public List<Integer> getChunkSizes() {
+        return chunkSizes;
+    }
+
+    /**
+     * Returns the total size of the body of this POST request (before
+     * truncation).
+     */
+    public int getBodySize() {
+        return bodySize;
+    }
+
+    /**
+     * Returns the body of this POST request. This may be truncated.
+     */
+    public byte[] getBody() {
+        return body;
+    }
+
+    /**
+     * Returns the index of this request on its HTTP connection. Since a single
+     * HTTP connection may serve multiple requests, each request is assigned its
+     * own sequence number.
+     */
+    public int getSequenceNumber() {
+        return sequenceNumber;
+    }
+
+    @Override public String toString() {
+        return requestLine;
+    }
+
+    public String getMethod() {
+        return getRequestLine().split(" ")[0];
+    }
+
+    public String getPath() {
+        return getRequestLine().split(" ")[1];
+    }
+}
diff --git a/docs/html/guide/developing/eclipse-adt.jd b/docs/html/guide/developing/eclipse-adt.jd
index 9c77ece..d0fc9b8 100644
--- a/docs/html/guide/developing/eclipse-adt.jd
+++ b/docs/html/guide/developing/eclipse-adt.jd
@@ -21,6 +21,7 @@
         <li><a href="#librarySetup">Setting up a library project</a></li>
         <li><a href="#libraryReference">Referencing a library project</a></li>
         <li><a href="#considerations">Development considerations</a></li>
+        <li><a href="#libraryMigrating">Migrating library projects to ADT 0.9.8</a></li>
       </ol>
     </li>
     <li><a href="#Tips">Eclipse Tips</a></li>
@@ -644,10 +645,6 @@
 is because the library project is compiled by the main project to use the
 correct resource IDs.</p>
 
-<p><strong>One library project cannot reference another</strong></p>
-
-<p>A library cannot depend on another library.</p>
-
 <p><strong>A library project can include a JAR library</strong></p>
 
 <p>You can develop a library project that itself includes a JAR library, however
@@ -664,13 +661,6 @@
 href="{@docRoot}guide/topics/manifest/uses-library-element.html"><code>&lt;uses-library&gt;</code></a>
 element. </p>
 
-<p><strong>Library project can not include AIDL files</strong></p>
-
-<p>The tools do not support the use of <a
-href="{@docRoot}guide/developing/tools/aidl.html">AIDL</a> files in a library project.
-Any AIDL files used by an application must be stored in the application project
-itself.</p>
-
 <p><strong>Library project can not include raw assets</strong></p>
 
 <p>The tools do not support the use of raw asset files in a library project.
@@ -730,8 +720,76 @@
 library project What is important is that the main project can reference the
 library project through a relative link.</p>
 
+<h3 id="libraryMigrating">Migrating library projects to ADT 0.9.8</h3>
 
-<h2 id="Tips">Eclipse Tips </h2>
+<p>This section provides information about how to migrate a library project
+created with ADT 0.9.7 to ADT 0.9.8 (or higher). The migration is needed only if
+you are developing in Eclipse with ADT and assumes that you have also upgraded
+to SDK Tools r7 (or higher). </p>
+
+<p>The way that ADT handles library projects has changed between
+ADT 0.9.7 and ADT 0.9.8. Specifically, in ADT 0.9.7, the <code>src/</code>
+source folder of the library was linked into the dependent application project
+as a folder that had the same name as the library project. This worked because
+of two restrictions on the library projects:</p>
+
+<ul>
+<li>The library was only able to contain a single source folder (excluding the
+special <code>gen/</code> source folder), and</li>
+<li>The source folder was required to have the name <code>src/</code> and be
+stored at the root of the project.</li>
+</ul>
+
+<p>In ADT 0.9.8, both of those restrictions were removed. A library project can
+have as many source folders as needed and each can have any name. Additionally,
+a library project can store source folders in any location of the project. For
+example, you could store sources in a <code>src/java/</code> directory. In order
+to support this, the name of the linked source folders in the main project are
+now called &lt;<em>library-name</em>&gt;_&lt;<em>folder-name</em>&gt; For
+example: <code>MyLibrary_src/</code> or <code>MyLibrary_src_java/</code>.</p>
+
+<p>Additionally, the linking process now flags those folders in order for ADT to
+recognize that it created them. This will allow ADT to automatically migrate the
+project to new versions of ADT, should they contain changes to the handling of
+library projects. ADT 0.9.7 did not flag the linked source folders, so ADT 0.9.8
+cannot be sure whether the old linked folders can be removed safely. After
+upgrading ADT to 0.9.8, you will need to remove the old linked folders manually
+in a simple two-step process, as described below.</p>
+
+<p>Before you begin, make sure to create a backup copy of your application or
+save the latest version to your code version control system. This ensures that
+you will be able to easily revert the migration changes in case there is a
+problem in your environment.</p>
+
+<p>When you first upgrade to ADT 0.9.8, your main project will look as shown
+below, with two linked folders (in this example, <code>MyLibrary</code> and
+<code>MyLibrary_src</code> &mdash; both of which link to
+<code>MyLibrary/src</code>. Eclipse shows an error on one of them because they
+are duplicate links to a single class.</p>
+
+<img src="{@docRoot}images/developing/lib-migration-0.png" alt="">
+
+<p>To fix the error, remove the linked folder that <em>does not</em> contain the
+<code>_src</code> suffix. </p>
+
+<ol>
+<li>Right click the folder that you want to remove (in this case, the
+<code>MyLibrary</code> folder) and choose <strong>Build Path</strong> &gt;
+<strong>Remove from Build Path</strong>, as shown below.</li>
+
+<img src="{@docRoot}images/developing/lib-migration-1.png" style="height:600px"
+alt="">
+
+<li>Next, When asked about unlinking the folder from the project, select
+<strong>Yes</strong>, as shown below.</li>
+
+<img src="{@docRoot}images/developing/lib-migration-2.png" alt="">
+</ol>
+
+<p>This should resolve the error and migrate your library project to the new
+ADT environment. </p>
+
+<h2 id="Tips">Eclipse Tips</h2>
 
 <h3 id="arbitraryexpressions">Executing arbitrary Java expressions in Eclipse</h3>
 
diff --git a/docs/html/guide/developing/other-ide.jd b/docs/html/guide/developing/other-ide.jd
index 1d67aa9..ff13f43 100644
--- a/docs/html/guide/developing/other-ide.jd
+++ b/docs/html/guide/developing/other-ide.jd
@@ -838,10 +838,6 @@
 is because the library project is compiled by the main project to use the
 correct resource IDs.</p>
 
-<p><strong>One library project cannot reference another</strong></p>
-
-<p>A library cannot depend on another library.</p>
-
 <p><strong>A library project can include a JAR library</strong></p>
 
 <p>You can develop a library project that itself includes a JAR library. When
@@ -858,13 +854,6 @@
 href="{@docRoot}guide/topics/manifest/uses-library-element.html"><code>&lt;uses-library&gt;</code></a>
 element. </p>
 
-<p><strong>Library project cannot include AIDL files</strong></p>
-
-<p>The tools do not support the use of <a
-href="{@docRoot}guide/developing/tools/aidl.html">AIDL</a> files in a library project.
-Any AIDL files used by an application must be stored in the application project
-itself.</p>
-
 <p><strong>Library project cannot include raw assets</strong></p>
 
 <p>The tools do not support the use of raw asset files in a library project.
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index 74b544b..d047b2d 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -472,18 +472,25 @@
             progressDialog = new ProgressDialog(NotificationTest.this);
             progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
             progressDialog.setMessage("Loading...");
-            progressThread = new ProgressThread(handler);
-            progressThread.start();
             return progressDialog;
         default:
             return null;
         }
     }
 
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        switch(id) {
+        case PROGRESS_DIALOG:
+            progressDialog.setProgress(0);
+            progressThread = new ProgressThread(handler);
+            progressThread.start();
+    }
+
     // Define the Handler that receives messages from the thread and update the progress
     final Handler handler = new Handler() {
         public void handleMessage(Message msg) {
-            int total = msg.getData().getInt("total");
+            int total = msg.arg1;
             progressDialog.setProgress(total);
             if (total >= 100){
                 dismissDialog(PROGRESS_DIALOG);
@@ -514,9 +521,7 @@
                     Log.e("ERROR", "Thread Interrupted");
                 }
                 Message msg = mHandler.obtainMessage();
-                Bundle b = new Bundle();
-                b.putInt("total", total);
-                msg.setData(b);
+                msg.arg1 = total;
                 mHandler.sendMessage(msg);
                 total++;
             }
diff --git a/docs/html/images/developing/lib-migration-0.png b/docs/html/images/developing/lib-migration-0.png
new file mode 100644
index 0000000..226b0a5
--- /dev/null
+++ b/docs/html/images/developing/lib-migration-0.png
Binary files differ
diff --git a/docs/html/images/developing/lib-migration-1.png b/docs/html/images/developing/lib-migration-1.png
new file mode 100644
index 0000000..f413dab
--- /dev/null
+++ b/docs/html/images/developing/lib-migration-1.png
Binary files differ
diff --git a/docs/html/images/developing/lib-migration-2.png b/docs/html/images/developing/lib-migration-2.png
new file mode 100644
index 0000000..0aa5849
--- /dev/null
+++ b/docs/html/images/developing/lib-migration-2.png
Binary files differ
diff --git a/docs/html/sdk/adt_download.jd b/docs/html/sdk/adt_download.jd
index f98caf5..126c052 100644
--- a/docs/html/sdk/adt_download.jd
+++ b/docs/html/sdk/adt_download.jd
@@ -22,11 +22,18 @@
     <th>Notes</th>
   </tr>
   <tr>
-     <td>0.9.7</td>
-     <td><a href="http://dl-ssl.google.com/android/ADT-0.9.7.zip">ADT-0.9.7.zip</a></td>
+     <td>0.9.8</td>
+     <td><a href="http://dl-ssl.google.com/android/ADT-0.9.8.zip">ADT-0.9.8.zip</a></td>
      <td><nobr>{@adtZipBytes} bytes</nobr></td>
      <td>{@adtZipChecksum}</td>
-     <td>Requires SDK Tools, Revision 6 <em><nobr>May 2010</nobr></em></td>
+     <td>Requires SDK Tools, Revision 7 <em><nobr>September 2010</nobr></em></td>
+  </tr>
+  <tr>
+     <td>0.9.7</td>
+     <td><a href="http://dl-ssl.google.com/android/ADT-0.9.7.zip">ADT-0.9.7.zip</a></td>
+     <td><nobr>8033750 bytes</nobr></td>
+     <td>de2431c8d4786d127ae5bfc95b4605df</td>
+     <td>Requires SDK Tools, Revision 5 <em><nobr>May 2010</nobr></em></td>
   </tr>
   <tr>
      <td>0.9.6</td>
diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd
index bd7eeed..9d6c3ab 100644
--- a/docs/html/sdk/eclipse-adt.jd
+++ b/docs/html/sdk/eclipse-adt.jd
@@ -95,8 +95,62 @@
 }
 </style>
 
+
+
+
 <div class="toggleable opened">
   <a href="#" onclick="return toggleDiv(this)">
+        <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px" />
+ADT 0.9.8</a> <em>(August 2010)</em>
+  <div class="toggleme">
+
+
+</ul>
+</dd>
+
+<dl>
+
+<dt>Dependencies:</dt>
+
+<dd><p>ADT 0.9.8 is designed for use with SDK Tools r7 and later. Before 
+updating to ADT 0.9.8, we highly recommend that you use the Android SDK and
+AVD Manager to install SDK Tools r7 into your SDK.</p></dd>
+
+<dt>General notes:</dt>
+<dd>
+<ul>
+<li>Adds a new Action, "Rename Application Package", to the Android Tools
+contextual menu. The Action does a full application package refactoring.
+<li>Adds support for library projects that don't have a source folder
+called <code>src/</code>. There is now support for any number of source folders,
+with no name restriction. They can even be in subfolder such as
+<code>src/java</code>. If you are already working with library projects created
+in ADT 0.9.7, see <a 
+href="{@docRoot}guide/developing/eclipse-adt.html#libraryMigrating">Migrating
+library projects to ADT 0.9.8</a> for important information about moving
+to the new ADT environment.</li>
+<li>Adds support for library projects that depend on other library
+projects.</li>
+<li>Adds support for additional resource qualifiers:
+<code>car</code>/<code>desk</code>, <code>night</code>/<code>notnight</code> and
+<code>navexposed</code>/<code>navhidden</code>.</li>
+<li>Adds more device screen types in the layout editor. All screen
+resolution/density combinations listed in the <a 
+href="{@docRoot}guide/practices/screens_support.html#range">Supporting
+Multiple Screens</a> are now available.</li>
+<li>Fixes problems with handling of library project names that
+contain characters that are incompatible with the Eclipse path variable.
+Now properly sets up the link between the main project and the library
+project.</li>
+</ul>
+</dd>
+</dl>
+ </div>
+</div>
+
+
+<div class="toggleable closed">
+  <a href="#" onclick="return toggleDiv(this)">
         <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px" />
 ADT 0.9.7</a> <em>(May 2010)</em>
   <div class="toggleme">
@@ -120,6 +174,7 @@
  </div>
 </div>
 
+
 <div class="toggleable closed">
   <a href="#" onclick="return toggleDiv(this)">
         <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px" />
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index 5e92253..7016eee 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -1,17 +1,17 @@
 page.title=Android SDK
 sdk.redirect=0
 
-sdk.win_download=android-sdk_r06-windows.zip
-sdk.win_bytes=23293160
-sdk.win_checksum=7c7fcec3c6b5c7c3df6ae654b27effb5
+sdk.win_download=android-sdk_r07-windows.zip
+sdk.win_bytes=23669664
+sdk.win_checksum=69c40c2d2e408b623156934f9ae574f0
 
-sdk.mac_download=android-sdk_r06-mac_86.zip
-sdk.mac_bytes=19108077
-sdk.mac_checksum=c92abf66a82c7a3f2b8493ebe025dd22
+sdk.mac_download=android-sdk_r07-mac_x86.zip
+sdk.mac_bytes=19229546
+sdk.mac_checksum=0f330ed3ebb36786faf6dc72b8acf819
 
-sdk.linux_download=android-sdk_r06-linux_86.tgz
-sdk.linux_bytes=16971139
-sdk.linux_checksum=848371e4bf068dbb582b709f4e56d903
+sdk.linux_download=android-sdk_r07-linux_x86.tgz
+sdk.linux_bytes=17114517
+sdk.linux_checksum=e10c75da3d1aa147ddd4a5c58bfc3646
 
 @jd:body
 
@@ -50,7 +50,7 @@
 <p><strong>4. Add Android platforms and other components to your SDK</strong></p>
 
 <p>Use the Android SDK and AVD Manager, included in the SDK starter package, to
-add one or more Android platforms (for example, Android 1.6 or Android 2.0) and
+add one or more Android platforms (for example, Android 1.6 or Android 2.2) and
 other components to your SDK. If you aren't sure what to add, see <a
 href="installing.html#which">Which components do I need?</a></p>
 
diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs
index 404e938..a665e95 100644
--- a/docs/html/sdk/sdk_toc.cs
+++ b/docs/html/sdk/sdk_toc.cs
@@ -75,8 +75,8 @@
       </li>
     </ul>
     <ul>
-      <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r6</a>
-      </li>
+      <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r7</a>
+      <span class="new">new!</span></li>
       <li><a href="<?cs var:toroot ?>sdk/win-usb.html">USB Driver for
       Windows, r3</a>
       </li>
@@ -94,7 +94,7 @@
       <span style="display:none" class="zh-TW"></span>
       </h2>
     <ul>
-      <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 0.9.7
+      <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 0.9.8
       <span style="display:none" class="de"></span>
       <span style="display:none" class="es"></span>
       <span style="display:none" class="fr"></span>
@@ -102,7 +102,7 @@
       <span style="display:none" class="ja"></span>
       <span style="display:none" class="zh-CN"></span>
       <span style="display:none" class="zh-TW"></span></a>
-      </li>
+      <span class="new">new!</span></li>
     </ul>
   </li>
   <li>
diff --git a/docs/html/sdk/tools-notes.jd b/docs/html/sdk/tools-notes.jd
index c9be6ff..dc58801 100644
--- a/docs/html/sdk/tools-notes.jd
+++ b/docs/html/sdk/tools-notes.jd
@@ -64,6 +64,39 @@
 <div class="toggleable opened">
   <a href="#" onclick="return toggleDiv(this)">
         <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px" />
+SDK Tools, Revision 7</a> <em>(September 2010)</em>
+  <div class="toggleme">
+
+<dl>
+<dt>Dependencies:</dt>
+<dd>
+<p>If you are developing in Eclipse with ADT, note that SDK Tools r7 is
+designed for use with ADT 0.9.8 and later. After installing SDK Tools r7, we
+highly recommend updating your ADT Plugin to 0.9.8.</p>
+</dd>
+
+<dt>General notes:</dt>
+<dd>
+<ul>
+<li>Added support for library projects that depend on other library projects.</li>
+<li>Adds support for aidl files in library projects.</li>
+<li>Adds support for extension targets in Ant build to perform tasks between the
+normal tasks: <code>-pre-build</code>, <code>-pre-compile</code>, and
+<code>-post-compile</code>.</li>
+<li>Adds support for "headless" SDK update. See <code>android -h update sdk</code>
+for more information.</li>
+<li>Fixes location control in DDMS to work in any locale not using '.' as a
+decimal point.</li>
+</li>
+</ul>
+</dd>
+</dl>
+ </div>
+</div>
+
+<div class="toggleable closed">
+  <a href="#" onclick="return toggleDiv(this)">
+        <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px" />
 SDK Tools, Revision 6</a> <em>(May 2010)</em>
   <div class="toggleme">
 
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d9ee3ec..9a19056 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -446,28 +446,30 @@
         Rect srcR = new Rect(x, y, x + width, y + height);
         RectF dstR = new RectF(0, 0, width, height);
 
+        final Config newConfig = source.getConfig() == Config.ARGB_8888 ?
+                Config.ARGB_8888 : Config.RGB_565;
+
         if (m == null || m.isIdentity()) {
-            bitmap = createBitmap(neww, newh,
-                    source.hasAlpha() ? Config.ARGB_8888 : Config.RGB_565);
+            bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
             paint = null;   // not needed
         } else {
-            /*  the dst should have alpha if the src does, or if our matrix
-                doesn't preserve rectness
-            */
-            boolean hasAlpha = source.hasAlpha() || !m.rectStaysRect();
+            final boolean transformed = !m.rectStaysRect();
+
             RectF deviceR = new RectF();
             m.mapRect(deviceR, dstR);
+
             neww = Math.round(deviceR.width());
             newh = Math.round(deviceR.height());
-            bitmap = createBitmap(neww, newh, hasAlpha ? Config.ARGB_8888 : Config.RGB_565);
-            if (hasAlpha) {
-                bitmap.eraseColor(0);
-            }
+
+            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
+                    transformed || source.hasAlpha());
+
             canvas.translate(-deviceR.left, -deviceR.top);
             canvas.concat(m);
+
             paint = new Paint();
             paint.setFilterBitmap(filter);
-            if (!m.rectStaysRect()) {
+            if (transformed) {
                 paint.setAntiAlias(true);
             }
         }
@@ -492,8 +494,30 @@
      * @throws IllegalArgumentException if the width or height are <= 0
      */
     public static Bitmap createBitmap(int width, int height, Config config) {
+        return createBitmap(width, height, config, true);
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is as per {@link #getDensity}.
+     *
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the
+     *                 bitmap as opaque. Doing so will clear the bitmap in black
+     *                 instead of transparent.  
+     * 
+     * @throws IllegalArgumentException if the width or height are <= 0
+     */
+    private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
         Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
-        bm.eraseColor(0);    // start with black/transparent pixels
+        if (config == Config.ARGB_8888 && !hasAlpha) {
+            bm.eraseColor(0xff000000);
+            nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha);
+        } else {
+            bm.eraseColor(0);
+        }
         return bm;
     }
 
@@ -1094,7 +1118,7 @@
     private static native void nativePrepareToDraw(int nativeBitmap);
     private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha);
     private static native boolean nativeSameAs(int nb0, int nb1);
-
+    
     /* package */ final int ni() {
         return mNativeBitmap;
     }
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 5dbbd70..dc21a72 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -83,7 +83,7 @@
         /**
          * The pixel density to use for the bitmap.  This will always result
          * in the returned bitmap having a density set for it (see
-         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int))}.  In addition,
+         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)}).  In addition,
          * if {@link #inScaled} is set (which it is by default} and this
          * density does not match {@link #inTargetDensity}, then the bitmap
          * will be scaled to the target density before being returned.
diff --git a/include/media/EffectEnvironmentalReverbApi.h b/include/media/EffectEnvironmentalReverbApi.h
index 2233e3f..36accd8 100644
--- a/include/media/EffectEnvironmentalReverbApi.h
+++ b/include/media/EffectEnvironmentalReverbApi.h
@@ -48,16 +48,16 @@
 
 //t_reverb_settings is equal to SLEnvironmentalReverbSettings defined in OpenSL ES specification.
 typedef struct s_reverb_settings {
-    int16_t roomLevel;
-    int16_t roomHFLevel;
-    int32_t decayTime;
-    int16_t decayHFRatio;
-    int16_t reflectionsLevel;
-    int32_t reflectionsDelay;
-    int16_t reverbLevel;
-    int32_t reverbDelay;
-    int16_t diffusion;
-    int16_t density;
+    int16_t     roomLevel;
+    int16_t     roomHFLevel;
+    uint32_t    decayTime;
+    int16_t     decayHFRatio;
+    int16_t     reflectionsLevel;
+    uint32_t    reflectionsDelay;
+    int16_t     reverbLevel;
+    uint32_t    reverbDelay;
+    int16_t     diffusion;
+    int16_t     density;
 } __attribute__((packed)) t_reverb_settings;
 
 
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 1e447f1..1594e31 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -49,6 +49,7 @@
     kKeyNTPTime           = 'ntpT',  // uint64_t (ntp-timestamp)
     kKeyTargetTime        = 'tarT',  // int64_t (usecs)
     kKeyDriftTime         = 'dftT',  // int64_t (usecs)
+    kKeyAnchorTime        = 'ancT',  // int64_t (usecs)
     kKeyDuration          = 'dura',  // int64_t (usecs)
     kKeyColorFormat       = 'colf',
     kKeyPlatformPrivate   = 'priv',  // pointer
diff --git a/include/utils/Singleton.h b/include/utils/Singleton.h
index 3b975b4..e1ee8eb 100644
--- a/include/utils/Singleton.h
+++ b/include/utils/Singleton.h
@@ -37,6 +37,11 @@
         }
         return *instance;
     }
+
+    static bool hasInstance() {
+        Mutex::Autolock _l(sLock);
+        return sInstance != 0;
+    }
     
 protected:
     ~Singleton() { };
diff --git a/keystore/java/android/security/SystemKeyStore.java b/keystore/java/android/security/SystemKeyStore.java
index abdb0ae..1093219 100644
--- a/keystore/java/android/security/SystemKeyStore.java
+++ b/keystore/java/android/security/SystemKeyStore.java
@@ -20,6 +20,8 @@
 import android.os.FileUtils;
 import android.os.Process;
 
+import org.apache.harmony.luni.util.InputStreamHelper;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -108,26 +110,19 @@
         return keyFile;
     }
 
-    public String retrieveKeyHexString(String keyName) {
+    public String retrieveKeyHexString(String keyName) throws IOException {
         return toHexString(retrieveKey(keyName));
     }
 
-    public byte[] retrieveKey(String keyName) {
-
+    public byte[] retrieveKey(String keyName) throws IOException {
         File keyFile = getKeyFile(keyName);
+
         if (!keyFile.exists()) {
             return null;
         }
 
-        try {
-            FileInputStream fis = new FileInputStream(keyFile);
-            int keyLen = fis.available();
-            byte[] retKey = new byte[keyLen];
-            fis.read(retKey);
-            fis.close();
-            return retKey;
-        } catch (IOException ioe) { }
-        throw new IllegalArgumentException();
+        FileInputStream fis = new FileInputStream(keyFile);
+        return InputStreamHelper.readFullyAndClose(fis);
     }
 
     public void deleteKey(String keyName) {
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index fda57b8..9c67885 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -17,7 +17,9 @@
 #ifndef ANDROID_UI_CACHES_H
 #define ANDROID_UI_CACHES_H
 
-#define LOG_TAG "OpenGLRenderer"
+#ifndef LOG_TAG
+    #define LOG_TAG "OpenGLRenderer"
+#endif
 
 #include <utils/Singleton.h>
 
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 7778290..d50d36e 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -26,17 +26,33 @@
 namespace android {
 namespace uirenderer {
 
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_EXTENSIONS 0
+
+// Debug
+#if DEBUG_EXTENSIONS
+    #define EXT_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define EXT_LOGD(...)
+#endif
+
 class Extensions {
 public:
     Extensions() {
         const char* buffer = (const char*) glGetString(GL_EXTENSIONS);
         const char* current = buffer;
         const char* head = current;
+        EXT_LOGD("Available GL extensions:");
         do {
             head = strchr(current, ' ');
             String8 s(current, head ? head - current : strlen(current));
             if (s.length()) {
                 mExtensionList.add(s);
+                EXT_LOGD("  %s", s.string());
             }
             current = head + 1;
         } while (head);
@@ -44,6 +60,7 @@
         mHasNPot = hasExtension("GL_OES_texture_npot");
         mHasDrawPath = hasExtension("GL_NV_draw_path");
         mHasCoverageSample = hasExtension("GL_NV_coverage_sample");
+        mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch");
 
         mExtensions = buffer;
     }
@@ -51,6 +68,7 @@
     inline bool hasNPot() const { return mHasNPot; }
     inline bool hasDrawPath() const { return mHasDrawPath; }
     inline bool hasCoverageSample() const { return mHasCoverageSample; }
+    inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
 
     bool hasExtension(const char* extension) const {
         const String8 s(extension);
@@ -69,6 +87,7 @@
     bool mHasNPot;
     bool mHasDrawPath;
     bool mHasCoverageSample;
+    bool mHasFramebufferFetch;
 }; // class Extensions
 
 }; // namespace uirenderer
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index c42a5d8..070e33f 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -61,6 +61,7 @@
 
     bool contains(K key) const;
     V get(K key);
+    K getKeyAt(uint32_t index) const;
     void put(K key, V value);
     V remove(K key);
     V removeOldest();
@@ -122,6 +123,11 @@
 }
 
 template<typename K, typename V>
+K GenerationCache<K, V>::getKeyAt(uint32_t index) const {
+    return mCache.keyAt(index);
+}
+
+template<typename K, typename V>
 V GenerationCache<K, V>::get(K key) {
     ssize_t index = mCache.indexOfKey(key);
     if (index >= 0) {
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 58920bd..9957370 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -21,6 +21,8 @@
 #include <SkCanvas.h>
 #include <SkGradientShader.h>
 
+#include <utils/threads.h>
+
 #include "GradientCache.h"
 #include "Properties.h"
 
@@ -52,6 +54,7 @@
 }
 
 GradientCache::~GradientCache() {
+    Mutex::Autolock _l(mLock);
     mCache.clear();
 }
 
@@ -60,14 +63,17 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 uint32_t GradientCache::getSize() {
+    Mutex::Autolock _l(mLock);
     return mSize;
 }
 
 uint32_t GradientCache::getMaxSize() {
+    Mutex::Autolock _l(mLock);
     return mMaxSize;
 }
 
 void GradientCache::setMaxSize(uint32_t maxSize) {
+    Mutex::Autolock _l(mLock);
     mMaxSize = maxSize;
     while (mSize > mMaxSize) {
         mCache.removeOldest();
@@ -79,6 +85,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void GradientCache::operator()(SkShader*& shader, Texture*& texture) {
+    // Already locked here
     if (shader) {
         const uint32_t size = texture->width * texture->height * 4;
         mSize -= size;
@@ -95,14 +102,17 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 Texture* GradientCache::get(SkShader* shader) {
+    Mutex::Autolock _l(mLock);
     return mCache.get(shader);
 }
 
 void GradientCache::remove(SkShader* shader) {
+    Mutex::Autolock _l(mLock);
     mCache.remove(shader);
 }
 
 void GradientCache::clear() {
+    Mutex::Autolock _l(mLock);
     mCache.clear();
 }
 
@@ -128,17 +138,21 @@
 
     canvas.drawRectCoords(0.0f, 0.0f, bitmap.width(), 1.0f, p);
 
+    mLock.lock();
     // Asume the cache is always big enough
     const uint32_t size = bitmap.rowBytes() * bitmap.height();
     while (mSize + size > mMaxSize) {
         mCache.removeOldest();
     }
+    mLock.unlock();
 
     Texture* texture = new Texture;
     generateTexture(&bitmap, texture);
 
+    mLock.lock();
     mSize += size;
     mCache.put(shader, texture);
+    mLock.unlock();
 
     return texture;
 }
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 8795920..51a8c01 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -82,6 +82,12 @@
 
     uint32_t mSize;
     uint32_t mMaxSize;
+
+    /**
+     * Used to access mCache and mSize. All methods are accessed from a single
+     * thread except for remove().
+     */
+    mutable Mutex mLock;
 }; // class GradientCache
 
 }; // namespace uirenderer
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 47ab355..6c90704 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -133,6 +133,7 @@
 
     glViewport(0, 0, mWidth, mHeight);
 
+    glDisable(GL_DITHER);
     glDisable(GL_SCISSOR_TEST);
 
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
@@ -239,10 +240,14 @@
 
     if (p) {
         alpha = p->getAlpha();
-        const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
-        if (!isMode) {
-            // Assume SRC_OVER
-            mode = SkXfermode::kSrcOver_Mode;
+        if (!mExtensions.hasFramebufferFetch()) {
+            const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+            if (!isMode) {
+                // Assume SRC_OVER
+                mode = SkXfermode::kSrcOver_Mode;
+            }
+        } else {
+            mode = getXfermode(p->getXfermode());
         }
     } else {
         mode = SkXfermode::kSrcOver_Mode;
@@ -518,11 +523,14 @@
     }
 
     SkXfermode::Mode mode;
-
-    const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
-    if (!isMode) {
-        // Assume SRC_OVER
-        mode = SkXfermode::kSrcOver_Mode;
+    if (!mExtensions.hasFramebufferFetch()) {
+        const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+        if (!isMode) {
+            // Assume SRC_OVER
+            mode = SkXfermode::kSrcOver_Mode;
+        }
+    } else {
+        mode = getXfermode(p->getXfermode());
     }
 
     // Skia draws using the color's alpha channel if < 255
@@ -730,11 +738,12 @@
          }
      }
 
+     // Setup the blending mode
+     chooseBlending(true, mode, description);
+
      // Build and use the appropriate shader
      useProgram(mCaches.programCache.get(description));
 
-     // Setup the blending mode
-     chooseBlending(true, mode);
      bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
      glUniform1i(mCaches.currentProgram->getUniform("sampler"), textureUnit);
 
@@ -836,9 +845,6 @@
 
     GLuint textureUnit = 0;
 
-    // Setup the blending mode
-    chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode);
-
     // Describe the required shaders
     ProgramDescription description;
     if (mShader) {
@@ -848,6 +854,9 @@
         mColorFilter->describe(description, mExtensions);
     }
 
+    // Setup the blending mode
+    chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description);
+
     // Build and use the appropriate shader
     useProgram(mCaches.programCache.get(description));
 
@@ -906,11 +915,11 @@
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
 
+    chooseBlending(blend || alpha < 1.0f, mode, description);
+
     useProgram(mCaches.programCache.get(description));
     mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
 
-    chooseBlending(blend || alpha < 1.0f, mode);
-
     // Texture
     bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0);
     glUniform1i(mCaches.currentProgram->getUniform("sampler"), 0);
@@ -938,23 +947,35 @@
     glDisableVertexAttribArray(texCoordsSlot);
 }
 
-void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied) {
+void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
+        ProgramDescription& description) {
     blend = blend || mode != SkXfermode::kSrcOver_Mode;
     if (blend) {
-        if (!mCaches.blend) {
-            glEnable(GL_BLEND);
-        }
+        if (mode < SkXfermode::kPlus_Mode) {
+            if (!mCaches.blend) {
+                glEnable(GL_BLEND);
+            }
 
-        GLenum sourceMode = gBlends[mode].src;
-        GLenum destMode = gBlends[mode].dst;
-        if (!isPremultiplied && sourceMode == GL_ONE) {
-            sourceMode = GL_SRC_ALPHA;
-        }
+            GLenum sourceMode = gBlends[mode].src;
+            GLenum destMode = gBlends[mode].dst;
 
-        if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) {
-            glBlendFunc(sourceMode, destMode);
-            mCaches.lastSrcMode = sourceMode;
-            mCaches.lastDstMode = destMode;
+            if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) {
+                glBlendFunc(sourceMode, destMode);
+                mCaches.lastSrcMode = sourceMode;
+                mCaches.lastDstMode = destMode;
+            }
+        } else {
+            // These blend modes are not supported by OpenGL directly and have
+            // to be implemented using shaders. Since the shader will perform
+            // the blending, turn blending off here
+            if (mExtensions.hasFramebufferFetch()) {
+                description.framebufferMode = mode;
+            }
+
+            if (mCaches.blend) {
+                glDisable(GL_BLEND);
+            }
+            blend = false;
         }
     } else if (mCaches.blend) {
         glDisable(GL_BLEND);
@@ -982,10 +1003,14 @@
 
 void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
     if (paint) {
-        const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode);
-        if (!isMode) {
-            // Assume SRC_OVER
-            *mode = SkXfermode::kSrcOver_Mode;
+        if (!mExtensions.hasFramebufferFetch()) {
+            const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode);
+            if (!isMode) {
+                // Assume SRC_OVER
+                *mode = SkXfermode::kSrcOver_Mode;
+            }
+        } else {
+            *mode = getXfermode(paint->getXfermode());
         }
 
         // Skia draws using the color's alpha channel if < 255
@@ -1001,6 +1026,13 @@
     }
 }
 
+SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) {
+    if (mode == NULL) {
+        return SkXfermode::kSrcOver_Mode;
+    }
+    return mode->fMode;
+}
+
 void OpenGLRenderer::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit) {
     glActiveTexture(gTextureUnits[textureUnit]);
     glBindTexture(GL_TEXTURE_2D, texture);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 0e90d20..50f42c2 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -322,7 +322,9 @@
      * Enable or disable blending as necessary. This function sets the appropriate
      * blend function based on the specified xfermode.
      */
-    inline void chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied = true);
+    inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description);
+
+    inline SkXfermode::Mode getXfermode(SkXfermode* mode);
 
     /**
      * Use the specified program with the current GL context. If the program is already
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 158c0cc..70e06a1 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -21,6 +21,8 @@
 #include <SkCanvas.h>
 #include <SkRect.h>
 
+#include <utils/threads.h>
+
 #include "PathCache.h"
 #include "Properties.h"
 
@@ -51,6 +53,7 @@
 }
 
 PathCache::~PathCache() {
+    Mutex::Autolock _l(mLock);
     mCache.clear();
 }
 
@@ -67,14 +70,17 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 uint32_t PathCache::getSize() {
+    Mutex::Autolock _l(mLock);
     return mSize;
 }
 
 uint32_t PathCache::getMaxSize() {
+    Mutex::Autolock _l(mLock);
     return mMaxSize;
 }
 
 void PathCache::setMaxSize(uint32_t maxSize) {
+    Mutex::Autolock _l(mLock);
     mMaxSize = maxSize;
     while (mSize > mMaxSize) {
         mCache.removeOldest();
@@ -99,14 +105,29 @@
 // Caching
 ///////////////////////////////////////////////////////////////////////////////
 
+void PathCache::remove(SkPath* path) {
+    Mutex::Autolock _l(mLock);
+    // TODO: Linear search...
+    for (uint32_t i = 0; i < mCache.size(); i++) {
+        if (mCache.getKeyAt(i).path == path) {
+            mCache.removeAt(i);
+        }
+    }
+}
+
 PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
     PathCacheEntry entry(path, paint);
+
+    mLock.lock();
     PathTexture* texture = mCache.get(entry);
+    mLock.unlock();
 
     if (!texture) {
         texture = addTexture(entry, path, paint);
     } else if (path->getGenerationID() != texture->generation) {
+        mLock.lock();
         mCache.remove(entry);
+        mLock.unlock();
         texture = addTexture(entry, path, paint);
     }
 
@@ -132,9 +153,11 @@
     const uint32_t size = width * height;
     // Don't even try to cache a bitmap that's bigger than the cache
     if (size < mMaxSize) {
+        mLock.lock();
         while (mSize + size > mMaxSize) {
             mCache.removeOldest();
         }
+        mLock.unlock();
     }
 
     PathTexture* texture = new PathTexture;
@@ -157,8 +180,10 @@
     generateTexture(bitmap, texture);
 
     if (size < mMaxSize) {
+        mLock.lock();
         mSize += size;
         mCache.put(entry, texture);
+        mLock.unlock();
     } else {
         texture->cleanup = true;
     }
@@ -167,6 +192,7 @@
 }
 
 void PathCache::clear() {
+    Mutex::Autolock _l(mLock);
     mCache.clear();
 }
 
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 522e5e0..bde0e7d 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -114,6 +114,10 @@
      * Clears the cache. This causes all textures to be deleted.
      */
     void clear();
+    /**
+     * Removes an entry.
+     */
+    void remove(SkPath* path);
 
     /**
      * Sets the maximum size of the cache in bytes.
@@ -143,6 +147,12 @@
     uint32_t mSize;
     uint32_t mMaxSize;
     GLuint mMaxTextureSize;
+
+    /**
+     * Used to access mCache and mSize. All methods are accessed from a single
+     * thread except for remove().
+     */
+    mutable Mutex mLock;
 }; // class PathCache
 
 }; // namespace uirenderer
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 39fe85a..ff65c1b 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -66,6 +66,8 @@
 // Fragment shaders snippets
 ///////////////////////////////////////////////////////////////////////////////
 
+const char* gFS_Header_Extension_FramebufferFetch =
+        "#extension GL_NV_shader_framebuffer_fetch : enable\n\n";
 const char* gFS_Header =
         "precision mediump float;\n\n";
 const char* gFS_Uniforms_Color =
@@ -115,6 +117,8 @@
         "    fragColor = bitmapColor * fragColor.a;\n";
 const char* gFS_Main_FragColor =
         "    gl_FragColor = fragColor;\n";
+const char* gFS_Main_FragColor_Blend =
+        "    gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n";
 const char* gFS_Main_ApplyColorOp[4] = {
         // None
         "",
@@ -281,7 +285,14 @@
 
 String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) {
     // Set the default precision
-    String8 shader(gFS_Header);
+    String8 shader;
+
+    bool blendFramebuffer = description.framebufferMode >= SkXfermode::kPlus_Mode;
+    if (blendFramebuffer) {
+        shader.append(gFS_Header_Extension_FramebufferFetch);
+    }
+
+    shader.append(gFS_Header);
 
     // Varyings
     if (description.hasTexture) {
@@ -315,6 +326,9 @@
     if (description.colorOp == ProgramDescription::kColorBlend) {
         generateBlend(shader, "blendColors", description.colorMode);
     }
+    if (blendFramebuffer) {
+        generateBlend(shader, "blendFramebuffer", description.framebufferMode);
+    }
     if (description.isBitmapNpot) {
         generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT);
     }
@@ -359,7 +373,11 @@
         // Apply the color op if needed
         shader.append(gFS_Main_ApplyColorOp[description.colorOp]);
         // Output the fragment
-        shader.append(gFS_Main_FragColor);
+        if (!blendFramebuffer) {
+            shader.append(gFS_Main_FragColor);
+        } else {
+            shader.append(gFS_Main_FragColor_Blend);
+        }
     }
     // End the shader
     shader.append(gFS_Footer);
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index f211ab6..8f5304d 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -35,7 +35,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // Debug
-#define DEBUG_PROGRAM_CACHE 0
+#define DEBUG_PROGRAM_CACHE 1
 
 // Debug
 #if DEBUG_PROGRAM_CACHE
@@ -61,6 +61,7 @@
 #define PROGRAM_MAX_XFERMODE 0x1f
 #define PROGRAM_XFERMODE_SHADER_SHIFT 26
 #define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
+#define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14
 
 #define PROGRAM_BITMAP_WRAPS_SHIFT 9
 #define PROGRAM_BITMAP_WRAPT_SHIFT 11
@@ -93,7 +94,8 @@
         hasBitmap(false), isBitmapNpot(false), hasGradient(false),
         shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false),
         bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE),
-        colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode) {
+        colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode),
+        framebufferMode(SkXfermode::kClear_Mode) {
     }
 
     // Texturing
@@ -113,6 +115,10 @@
     int colorOp;
     SkXfermode::Mode colorMode;
 
+    // Framebuffer blending (requires Extensions.hasFramebufferFetch())
+    // Ignored for all values < SkXfermode::kPlus_Mode
+    SkXfermode::Mode framebufferMode;
+
     inline uint32_t getEnumForWrap(GLenum wrap) const {
         switch (wrap) {
             case GL_CLAMP_TO_EDGE:
@@ -156,6 +162,7 @@
             case kColorNone:
                 break;
         }
+        key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
         return key;
     }
 }; // struct ProgramDescription
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index e558870..927070a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -102,9 +102,10 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 Texture* TextureCache::get(SkBitmap* bitmap) {
-    Mutex::Autolock _l(mLock);
-
+    mLock.lock();
     Texture* texture = mCache.get(bitmap);
+    mLock.unlock();
+
     if (!texture) {
         if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
             LOGW("Bitmap too large to be uploaded into a texture");
@@ -114,9 +115,11 @@
         const uint32_t size = bitmap->rowBytes() * bitmap->height();
         // Don't even try to cache a bitmap that's bigger than the cache
         if (size < mMaxSize) {
+            mLock.lock();
             while (mSize + size > mMaxSize) {
                 mCache.removeOldest();
             }
+            mLock.unlock();
         }
 
         texture = new Texture;
@@ -124,8 +127,10 @@
         generateTexture(bitmap, texture, false);
 
         if (size < mMaxSize) {
+            mLock.lock();
             mSize += size;
             mCache.put(bitmap, texture);
+            mLock.unlock();
         } else {
             texture->cleanup = true;
         }
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 847d69c..a63789a 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -87,6 +87,10 @@
     uint32_t mMaxSize;
     GLint mMaxTextureSize;
 
+    /**
+     * Used to access mCache and mSize. All methods are accessed from a single
+     * thread except for remove().
+     */
     mutable Mutex mLock;
 }; // class TextureCache
 
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 7d8ef58..8ca6237 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1175,9 +1175,12 @@
             mGenreCache = new HashMap<String, Uri>();
             mGenresUri = Genres.getContentUri(volumeName);
             mPlaylistsUri = Playlists.getContentUri(volumeName);
-            // assuming external storage is FAT (case insensitive), except on the simulator.
-            if ( Process.supportsProcesses()) {
-                mCaseInsensitivePaths = true;
+
+            mCaseInsensitivePaths = !mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_caseSensitiveExternalStorage);
+            if (!Process.supportsProcesses()) {
+                // Simulator uses host file system, so it should be case sensitive.
+                mCaseInsensitivePaths = false;
             }
         }
     }
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index 3487b0f..ad029a6 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -89,7 +89,7 @@
         mContext = context;
         mMediaProvider = context.getContentResolver().acquireProvider("media");
         mVolumeName = volumeName;
-        mObjectsUri = Files.getContentUri(volumeName);
+        mObjectsUri = Files.getMtpObjectsUri(volumeName);
         mMediaScanner = new MediaScanner(context);
         openDevicePropertiesDatabase(context);
     }
@@ -246,6 +246,8 @@
         return new int[] {
             // allow transfering arbitrary files
             MtpConstants.FORMAT_UNDEFINED,
+            MtpConstants.FORMAT_ASSOCIATION,
+            MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
         };
     }
 
@@ -479,7 +481,7 @@
     private int deleteFile(int handle) {
         Log.d(TAG, "deleteFile: " + handle);
         mDatabaseModified = true;
-        Uri uri = Files.getContentUri(mVolumeName, handle);
+        Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
         try {
             if (mMediaProvider.delete(uri, null, null) == 1) {
                 return MtpConstants.RESPONSE_OK;
@@ -494,7 +496,7 @@
 
     private int[] getObjectReferences(int handle) {
         Log.d(TAG, "getObjectReferences for: " + handle);
-        Uri uri = Files.getReferencesUri(mVolumeName, handle);
+        Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
         Cursor c = null;
         try {
             c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null);
@@ -522,7 +524,7 @@
 
     private int setObjectReferences(int handle, int[] references) {
         mDatabaseModified = true;
-        Uri uri = Files.getReferencesUri(mVolumeName, handle);
+        Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
         int count = references.length;
         ContentValues[] valuesList = new ContentValues[count];
         for (int i = 0; i < count; i++) {
diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
index 45ef416..b3e1531 100755
--- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
+++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
@@ -1133,7 +1133,7 @@
     //LOGV("\tReverbSetDecayTime() just Got -> %d\n", ActiveParams.T60);
 
     if (time <= LVREV_MAX_T60) {
-        ActiveParams.T60 = time;
+        ActiveParams.T60 = (LVM_UINT16)time;
     }
     else {
         ActiveParams.T60 = LVREV_MAX_T60;
@@ -1146,7 +1146,7 @@
 
     pContext->SamplesToExitCount = (ActiveParams.T60 * pContext->config.inputCfg.samplingRate)/1000;
     //LOGV("\tReverbSetDecayTime() just Set SamplesToExitCount-> %d\n",pContext->SamplesToExitCount);
-    pContext->SavedDecayTime = time;
+    pContext->SavedDecayTime = (int16_t)time;
     //LOGV("\tReverbSetDecayTime end");
     return;
 }
@@ -1162,7 +1162,7 @@
 //
 //----------------------------------------------------------------------------
 
-int32_t ReverbGetDecayTime(ReverbContext *pContext){
+uint32_t ReverbGetDecayTime(ReverbContext *pContext){
     //LOGV("\tReverbGetDecayTime start");
 
     LVREV_ControlParams_st    ActiveParams;              /* Current control Parameters */
@@ -1181,7 +1181,7 @@
     }
 
     //LOGV("\tReverbGetDecayTime end");
-    return ActiveParams.T60;
+    return (uint32_t)ActiveParams.T60;
 }
 
 //----------------------------------------------------------------------------
@@ -1606,7 +1606,7 @@
             //        *(int16_t *)pValue);
             break;
         case REVERB_PARAM_DECAY_TIME:
-            *(int32_t *)pValue = ReverbGetDecayTime(pContext);
+            *(uint32_t *)pValue = ReverbGetDecayTime(pContext);
 
             //LOGV("\tReverb_getParameter() REVERB_PARAM_DECAY_TIME Value is %d",
             //        *(int32_t *)pValue);
@@ -1671,6 +1671,7 @@
 int Reverb_setParameter (ReverbContext *pContext, void *pParam, void *pValue){
     int status = 0;
     int16_t level;
+    int16_t ratio;
     uint32_t time;
     t_reverb_settings *pProperties;
     int32_t *pParamTemp = (int32_t *)pParam;
@@ -1688,6 +1689,7 @@
             return -EINVAL;
         }
         pContext->nextPreset = preset;
+        return 0;
     }
 
     switch (param){
@@ -1724,10 +1726,10 @@
             //LOGV("\tReverb_setParameter() Called ReverbSetDecayTime");
            break;
         case REVERB_PARAM_DECAY_HF_RATIO:
-            time = *(int16_t *)pValue;
-            //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_HF_RATIO value is %d", time);
+            ratio = *(int16_t *)pValue;
+            //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_HF_RATIO value is %d", ratio);
             //LOGV("\tReverb_setParameter() Calling ReverbSetDecayHfRatio");
-            ReverbSetDecayHfRatio(pContext, time);
+            ReverbSetDecayHfRatio(pContext, ratio);
             //LOGV("\tReverb_setParameter() Called ReverbSetDecayHfRatio");
             break;
          case REVERB_PARAM_REVERB_LEVEL:
@@ -1738,17 +1740,17 @@
             //LOGV("\tReverb_setParameter() Called ReverbSetReverbLevel");
            break;
         case REVERB_PARAM_DIFFUSION:
-            time = *(int16_t *)pValue;
-            //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DIFFUSION value is %d", time);
+            ratio = *(int16_t *)pValue;
+            //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DIFFUSION value is %d", ratio);
             //LOGV("\tReverb_setParameter() Calling ReverbSetDiffusion");
-            ReverbSetDiffusion(pContext, time);
+            ReverbSetDiffusion(pContext, ratio);
             //LOGV("\tReverb_setParameter() Called ReverbSetDiffusion");
             break;
         case REVERB_PARAM_DENSITY:
-            time = *(int16_t *)pValue;
-            //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DENSITY value is %d", time);
+            ratio = *(int16_t *)pValue;
+            //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DENSITY value is %d", ratio);
             //LOGV("\tReverb_setParameter() Calling ReverbSetDensity");
-            ReverbSetDensity(pContext, time);
+            ReverbSetDensity(pContext, ratio);
             //LOGV("\tReverb_setParameter() Called ReverbSetDensity");
             break;
            break;
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index c2f79e8..8d7ada3 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -316,8 +316,10 @@
         }
 
         if (numFramesRecorded == 0) {
-            buffer->meta_data()->setInt64(kKeyTime, mStartTimeUs);
+            buffer->meta_data()->setInt64(kKeyAnchorTime, mStartTimeUs);
         }
+
+        buffer->meta_data()->setInt64(kKeyTime, mStartTimeUs + mPrevSampleTimeUs);
         buffer->meta_data()->setInt64(kKeyDriftTime, readTimeUs - mInitialReadTimeUs);
         CHECK(timestampUs > mPrevSampleTimeUs);
         mPrevSampleTimeUs = timestampUs;
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 2e36968..82c0426 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -459,7 +459,8 @@
 
 MP3Extractor::MP3Extractor(
         const sp<DataSource> &source, const sp<AMessage> &meta)
-    : mDataSource(source),
+    : mInitCheck(NO_INIT),
+      mDataSource(source),
       mFirstFramePos(-1),
       mFixedHeader(0),
       mByteNumber(0) {
@@ -480,53 +481,54 @@
         success = true;
     } else {
         success = Resync(mDataSource, 0, &pos, &header);
-        CHECK(success);
     }
 
-    if (success) {
-        mFirstFramePos = pos;
-        mFixedHeader = header;
+    if (!success) {
+        // mInitCheck will remain NO_INIT
+        return;
+    }
 
-        size_t frame_size;
-        int sample_rate;
-        int num_channels;
-        int bitrate;
-        get_mp3_frame_size(
-                header, &frame_size, &sample_rate, &num_channels, &bitrate);
+    mFirstFramePos = pos;
+    mFixedHeader = header;
 
-        mMeta = new MetaData;
+    size_t frame_size;
+    int sample_rate;
+    int num_channels;
+    int bitrate;
+    get_mp3_frame_size(
+            header, &frame_size, &sample_rate, &num_channels, &bitrate);
 
-        mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
-        mMeta->setInt32(kKeySampleRate, sample_rate);
-        mMeta->setInt32(kKeyBitRate, bitrate * 1000);
-        mMeta->setInt32(kKeyChannelCount, num_channels);
+    mMeta = new MetaData;
 
-        int64_t duration;
-        parse_xing_header(
-                mDataSource, mFirstFramePos, NULL, &mByteNumber,
-                mTableOfContents, NULL, &duration);
-        if (duration > 0) {
-            mMeta->setInt64(kKeyDuration, duration);
-        } else {
-            off_t fileSize;
-            if (mDataSource->getSize(&fileSize) == OK) {
-                mMeta->setInt64(
-                        kKeyDuration,
-                        8000LL * (fileSize - mFirstFramePos) / bitrate);
-            }
+    mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+    mMeta->setInt32(kKeySampleRate, sample_rate);
+    mMeta->setInt32(kKeyBitRate, bitrate * 1000);
+    mMeta->setInt32(kKeyChannelCount, num_channels);
+
+    int64_t duration;
+    parse_xing_header(
+            mDataSource, mFirstFramePos, NULL, &mByteNumber,
+            mTableOfContents, NULL, &duration);
+    if (duration > 0) {
+        mMeta->setInt64(kKeyDuration, duration);
+    } else {
+        off_t fileSize;
+        if (mDataSource->getSize(&fileSize) == OK) {
+            mMeta->setInt64(
+                    kKeyDuration,
+                    8000LL * (fileSize - mFirstFramePos) / bitrate);
         }
     }
-}
 
-MP3Extractor::~MP3Extractor() {
+    mInitCheck = OK;
 }
 
 size_t MP3Extractor::countTracks() {
-    return (mFirstFramePos < 0) ? 0 : 1;
+    return mInitCheck != OK ? 0 : 1;
 }
 
 sp<MediaSource> MP3Extractor::getTrack(size_t index) {
-    if (mFirstFramePos < 0 || index != 0) {
+    if (mInitCheck != OK || index != 0) {
         return NULL;
     }
 
@@ -536,7 +538,7 @@
 }
 
 sp<MetaData> MP3Extractor::getTrackMetaData(size_t index, uint32_t flags) {
-    if (mFirstFramePos < 0 || index != 0) {
+    if (mInitCheck != OK || index != 0) {
         return NULL;
     }
 
@@ -713,7 +715,7 @@
 sp<MetaData> MP3Extractor::getMetaData() {
     sp<MetaData> meta = new MetaData;
 
-    if (mFirstFramePos < 0) {
+    if (mInitCheck != OK) {
         return meta;
     }
 
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index ba90407..4bbc251 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -872,7 +872,11 @@
             }
 
             size_t max_size;
-            CHECK_EQ(mLastTrack->sampleTable->getMaxSampleSize(&max_size), OK);
+            err = mLastTrack->sampleTable->getMaxSampleSize(&max_size);
+
+            if (err != OK) {
+                return err;
+            }
 
             // Assume that a given buffer only contains at most 10 fragments,
             // each fragment originally prefixed with a 2 byte length will
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 806836d..de4233d 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -346,9 +346,10 @@
         // If file size is set to be larger than the 32 bit file
         // size limit, treat it as an error.
         if (mMaxFileSizeLimitBytes > kMax32BitFileSize) {
-            LOGE("32-bit file size limit too big: %lld bytes",
-                mMaxFileSizeLimitBytes);
-            return UNKNOWN_ERROR;
+            LOGW("32-bi file size limit (%lld bytes) too big. "
+                 "It is changed to %lld bytes",
+                mMaxFileSizeLimitBytes, kMax32BitFileSize);
+            mMaxFileSizeLimitBytes = kMax32BitFileSize;
         }
     }
 
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
index 0dacb53..5a453e9 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -26,6 +26,8 @@
 
 #include "include/TimedEventQueue.h"
 
+#include <cutils/sched_policy.h>
+
 #include <sys/prctl.h>
 #include <sys/time.h>
 #include <sys/resource.h>
@@ -209,6 +211,8 @@
 #endif
 
     setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_FOREGROUND);
+    set_sched_policy(androidGetTid(), SP_FOREGROUND);
+
     static_cast<TimedEventQueue *>(me)->threadEntry();
 
 #ifdef ANDROID_SIMULATOR
diff --git a/media/libstagefright/codecs/aacenc/AACEncoder.cpp b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
index e391c72..df9f107 100644
--- a/media/libstagefright/codecs/aacenc/AACEncoder.cpp
+++ b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
@@ -243,7 +243,7 @@
             if (mInputBuffer->meta_data()->findInt64(kKeyDriftTime, &timeUs)) {
                 wallClockTimeUs = timeUs;
             }
-            if (mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
+            if (mInputBuffer->meta_data()->findInt64(kKeyAnchorTime, &timeUs)) {
                 mAnchorTimeUs = timeUs;
             }
             readFromSource = true;
diff --git a/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp b/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
index 858e6d0..94a79ab 100644
--- a/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
+++ b/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
@@ -174,7 +174,7 @@
             if (mInputBuffer->meta_data()->findInt64(kKeyDriftTime, &timeUs)) {
                 wallClockTimeUs = timeUs;
             }
-            if (mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
+            if (mInputBuffer->meta_data()->findInt64(kKeyAnchorTime, &timeUs)) {
                 mAnchorTimeUs = timeUs;
             }
         } else {
diff --git a/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp b/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
index cd28413..002f055 100644
--- a/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
+++ b/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
@@ -224,7 +224,7 @@
             if (mInputBuffer->meta_data()->findInt64(kKeyDriftTime, &timeUs)) {
                 wallClockTimeUs = timeUs;
             }
-            if (mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
+            if (mInputBuffer->meta_data()->findInt64(kKeyAnchorTime, &timeUs)) {
                 mAnchorTimeUs = timeUs;
             }
             readFromSource = true;
diff --git a/media/libstagefright/include/MP3Extractor.h b/media/libstagefright/include/MP3Extractor.h
index 0e6ccde..30136e7d 100644
--- a/media/libstagefright/include/MP3Extractor.h
+++ b/media/libstagefright/include/MP3Extractor.h
@@ -37,10 +37,9 @@
 
     virtual sp<MetaData> getMetaData();
 
-protected:
-    virtual ~MP3Extractor();
-
 private:
+    status_t mInitCheck;
+
     sp<DataSource> mDataSource;
     off_t mFirstFramePos;
     sp<MetaData> mMeta;
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 9228b0b..bd5fad2 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -240,7 +240,7 @@
         memset(images, 0, sizeof(images));
     }
     EGLDisplay dpy;
-    EGLConfig context;
+    EGLContext context;
     EGLImageKHR images[IMPL_NUM_IMPLEMENTATIONS];
 };
 
@@ -1770,7 +1770,7 @@
          egl_connection_t* const cnx = &gEGLImpl[i];
          if (image->images[i] != EGL_NO_IMAGE_KHR) {
              if (cnx->dso) {
-                 if (cnx->egl.eglCreateImageKHR) {
+                 if (cnx->egl.eglDestroyImageKHR) {
                      if (cnx->egl.eglDestroyImageKHR(
                              dp->disp[i].dpy, image->images[i])) {
                          success = true;
diff --git a/opengl/tests/angeles/app-linux.cpp b/opengl/tests/angeles/app-linux.cpp
index 06fa0c2..4d10ee5 100644
--- a/opengl/tests/angeles/app-linux.cpp
+++ b/opengl/tests/angeles/app-linux.cpp
@@ -63,7 +63,7 @@
 int gAppAlive = 1;
 
 static const char sAppName[] =
-    "San Angeles Observation OpenGL ES version example (Linux)";
+        "San Angeles Observation OpenGL ES version example (Linux)";
 
 static int sWindowWidth = WINDOW_DEFAULT_WIDTH;
 static int sWindowHeight = WINDOW_DEFAULT_HEIGHT;
@@ -74,22 +74,22 @@
 const char *egl_strerror(unsigned err)
 {
     switch(err){
-    case EGL_SUCCESS: return "SUCCESS";
-    case EGL_NOT_INITIALIZED: return "NOT INITIALIZED";
-    case EGL_BAD_ACCESS: return "BAD ACCESS";
-    case EGL_BAD_ALLOC: return "BAD ALLOC";
-    case EGL_BAD_ATTRIBUTE: return "BAD_ATTRIBUTE";
-    case EGL_BAD_CONFIG: return "BAD CONFIG";
-    case EGL_BAD_CONTEXT: return "BAD CONTEXT";
-    case EGL_BAD_CURRENT_SURFACE: return "BAD CURRENT SURFACE";
-    case EGL_BAD_DISPLAY: return "BAD DISPLAY";
-    case EGL_BAD_MATCH: return "BAD MATCH";
-    case EGL_BAD_NATIVE_PIXMAP: return "BAD NATIVE PIXMAP";
-    case EGL_BAD_NATIVE_WINDOW: return "BAD NATIVE WINDOW";
-    case EGL_BAD_PARAMETER: return "BAD PARAMETER";
-    case EGL_BAD_SURFACE: return "BAD_SURFACE";
-//    case EGL_CONTEXT_LOST: return "CONTEXT LOST";
-    default: return "UNKNOWN";
+        case EGL_SUCCESS: return "SUCCESS";
+        case EGL_NOT_INITIALIZED: return "NOT INITIALIZED";
+        case EGL_BAD_ACCESS: return "BAD ACCESS";
+        case EGL_BAD_ALLOC: return "BAD ALLOC";
+        case EGL_BAD_ATTRIBUTE: return "BAD_ATTRIBUTE";
+        case EGL_BAD_CONFIG: return "BAD CONFIG";
+        case EGL_BAD_CONTEXT: return "BAD CONTEXT";
+        case EGL_BAD_CURRENT_SURFACE: return "BAD CURRENT SURFACE";
+        case EGL_BAD_DISPLAY: return "BAD DISPLAY";
+        case EGL_BAD_MATCH: return "BAD MATCH";
+        case EGL_BAD_NATIVE_PIXMAP: return "BAD NATIVE PIXMAP";
+        case EGL_BAD_NATIVE_WINDOW: return "BAD NATIVE WINDOW";
+        case EGL_BAD_PARAMETER: return "BAD PARAMETER";
+        case EGL_BAD_SURFACE: return "BAD_SURFACE";
+        //    case EGL_CONTEXT_LOST: return "CONTEXT LOST";
+        default: return "UNKNOWN";
     }
 }
 
@@ -118,52 +118,59 @@
         fprintf(stderr, "EGL Error: 0x%04x\n", (int)error);
 }
 
-static int initGraphics()
+static int initGraphics(unsigned samples)
 {
     EGLint configAttribs[] = {
-         EGL_DEPTH_SIZE, 16,
-         EGL_NONE
-     };
-     
-     EGLint majorVersion;
-     EGLint minorVersion;
-     EGLContext context;
-     EGLConfig config;
-     EGLSurface surface;
-     EGLint w, h;
-     EGLDisplay dpy;
+            EGL_DEPTH_SIZE, 16,
+            EGL_SAMPLE_BUFFERS, samples ? 1 : 0,
+                    EGL_SAMPLES, samples,
+                    EGL_NONE
+    };
 
-     EGLNativeWindowType window = android_createDisplaySurface();
-     
-     dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-     eglInitialize(dpy, &majorVersion, &minorVersion);
-          
-     status_t err = EGLUtils::selectConfigForNativeWindow(
-             dpy, configAttribs, window, &config);
-     if (err) {
-         fprintf(stderr, "couldn't find an EGLConfig matching the screen format\n");
-         return 0;
-     }
+    EGLint majorVersion;
+    EGLint minorVersion;
+    EGLContext context;
+    EGLConfig config;
+    EGLSurface surface;
+    EGLint w, h;
+    EGLDisplay dpy;
 
-     surface = eglCreateWindowSurface(dpy, config, window, NULL);
-     egl_error("eglCreateWindowSurface");
+    EGLNativeWindowType window = android_createDisplaySurface();
 
-     fprintf(stderr,"surface = %p\n", surface);
+    dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    eglInitialize(dpy, &majorVersion, &minorVersion);
 
-     context = eglCreateContext(dpy, config, NULL, NULL);
-     egl_error("eglCreateContext");
-     fprintf(stderr,"context = %p\n", context);
-     
-     eglMakeCurrent(dpy, surface, surface, context);   
-     egl_error("eglMakeCurrent");
+    status_t err = EGLUtils::selectConfigForNativeWindow(
+            dpy, configAttribs, window, &config);
+    if (err) {
+        fprintf(stderr, "couldn't find an EGLConfig matching the screen format\n");
+        return 0;
+    }
 
-     eglQuerySurface(dpy, surface, EGL_WIDTH, &sWindowWidth);
-     eglQuerySurface(dpy, surface, EGL_HEIGHT, &sWindowHeight);
+    surface = eglCreateWindowSurface(dpy, config, window, NULL);
+    egl_error("eglCreateWindowSurface");
+
+    fprintf(stderr,"surface = %p\n", surface);
+
+    context = eglCreateContext(dpy, config, NULL, NULL);
+    egl_error("eglCreateContext");
+    fprintf(stderr,"context = %p\n", context);
+
+    eglMakeCurrent(dpy, surface, surface, context);
+    egl_error("eglMakeCurrent");
+
+    eglQuerySurface(dpy, surface, EGL_WIDTH, &sWindowWidth);
+    eglQuerySurface(dpy, surface, EGL_HEIGHT, &sWindowHeight);
 
     sEglDisplay = dpy;
     sEglSurface = surface;
     sEglContext = context;
 
+    if (samples == 0) {
+        // GL_MULTISAMPLE is enabled by default
+        glDisable(GL_MULTISAMPLE);
+    }
+
     return EGL_TRUE;
 }
 
@@ -179,35 +186,47 @@
 
 int main(int argc, char *argv[])
 {
-    // not referenced:
-    argc = argc;
-    argv = argv;
+    unsigned samples = 0;
+    printf("usage: %s [samples]\n", argv[0]);
+    if (argc == 2) {
+        samples = atoi( argv[1] );
+        printf("Multisample enabled: GL_SAMPLES = %u\n", samples);
+    }
 
-    if (!initGraphics())
+    if (!initGraphics(samples))
     {
         fprintf(stderr, "Graphics initialization failed.\n");
         return EXIT_FAILURE;
     }
 
     appInit();
-    
+
+    struct timeval timeTemp;
+    int frameCount = 0;
+    gettimeofday(&timeTemp, NULL);
+    double totalTime = timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec;
+
     while (gAppAlive)
     {
         struct timeval timeNow;
 
-        if (gAppAlive)
-        {
-            gettimeofday(&timeNow, NULL);
-            appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
-                      sWindowWidth, sWindowHeight);
-            checkGLErrors();
-            eglSwapBuffers(sEglDisplay, sEglSurface);
-            checkEGLErrors();
-        }
+        gettimeofday(&timeNow, NULL);
+        appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
+                sWindowWidth, sWindowHeight);
+        checkGLErrors();
+        eglSwapBuffers(sEglDisplay, sEglSurface);
+        checkEGLErrors();
+        frameCount++;
     }
 
+    gettimeofday(&timeTemp, NULL);
+
     appDeinit();
     deinitGraphics();
 
+    totalTime = (timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec) - totalTime;
+    printf("totalTime=%f s, frameCount=%d, %.2f fps\n",
+            totalTime, frameCount, frameCount/totalTime);
+
     return EXIT_SUCCESS;
 }
diff --git a/opengl/tests/gl_perf/fill_common.cpp b/opengl/tests/gl_perf/fill_common.cpp
index 36db1b0..a069f67 100644
--- a/opengl/tests/gl_perf/fill_common.cpp
+++ b/opengl/tests/gl_perf/fill_common.cpp
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
+#include "fragment_shaders.cpp"
+
 FILE * fOut = NULL;
 void ptSwap();
 
+static char gCurrentTestName[1024];
+static uint32_t gWidth = 0;
+static uint32_t gHeight = 0;
+
 static void checkGlError(const char* op) {
     for (GLint error = glGetError(); error; error
             = glGetError()) {
@@ -112,20 +118,21 @@
     gTime = getTime();
 }
 
-void endTimer(const char *str, int w, int h, double dc, int count) {
+
+static void endTimer(int count) {
     uint64_t t2 = getTime();
     double delta = ((double)(t2 - gTime)) / 1000000000;
-    double pixels = dc * (w * h) * count;
+    double pixels = (gWidth * gHeight) * count;
     double mpps = pixels / delta / 1000000;
-    double dc60 = pixels / delta / (w * h) / 60;
+    double dc60 = ((double)count) / delta / 60;
 
     if (fOut) {
-        fprintf(fOut, "%s, %f, %f\r\n", str, mpps, dc60);
+        fprintf(fOut, "%s, %f, %f\r\n", gCurrentTestName, mpps, dc60);
         fflush(fOut);
     } else {
-        printf("%s, %f, %f\n", str, mpps, dc60);
+        printf("%s, %f, %f\n", gCurrentTestName, mpps, dc60);
     }
-    LOGI("%s, %f, %f\r\n", str, mpps, dc60);
+    LOGI("%s, %f, %f\r\n", gCurrentTestName, mpps, dc60);
 }
 
 
@@ -137,86 +144,17 @@
     "varying vec4 v_color;\n"
     "varying vec2 v_tex0;\n"
     "varying vec2 v_tex1;\n"
+    "uniform vec2 u_texOff;\n"
 
     "void main() {\n"
     "    v_color = a_color;\n"
     "    v_tex0 = a_tex0;\n"
     "    v_tex1 = a_tex1;\n"
+    "    v_tex0.x += u_texOff.x;\n"
+    "    v_tex1.y += u_texOff.y;\n"
     "    gl_Position = a_pos;\n"
     "}\n";
 
-static const char gShaderPrefix[] =
-    "precision mediump float;\n"
-    "uniform vec4 u_color;\n"
-    "uniform vec4 u_0;\n"
-    "uniform vec4 u_1;\n"
-    "uniform vec4 u_2;\n"
-    "uniform vec4 u_3;\n"
-    "varying vec4 v_color;\n"
-    "varying vec2 v_tex0;\n"
-    "varying vec2 v_tex1;\n"
-    "uniform sampler2D u_tex0;\n"
-    "uniform sampler2D u_tex1;\n"
-    "void main() {\n";
-
-static const char gShaderPostfix[] =
-    "  gl_FragColor = c;\n"
-    "}\n";
-
-
-static char * append(char *d, const char *s) {
-    size_t len = strlen(s);
-    memcpy(d, s, len);
-    return d + len;
-}
-
-static char * genShader(
-    bool useVarColor,
-    int texCount,
-    bool modulateFirstTex,
-    int extraMath)
-{
-    char *str = (char *)calloc(16 * 1024, 1);
-    char *tmp = append(str, gShaderPrefix);
-
-    if (modulateFirstTex || !texCount) {
-        if (useVarColor) {
-            tmp = append(tmp, "  vec4 c = v_color;\n");
-        } else {
-            tmp = append(tmp, "  vec4 c = u_color;\n");
-        }
-    } else {
-        tmp = append(tmp, "  vec4 c = texture2D(u_tex0, v_tex0);\n");
-    }
-
-    if (modulateFirstTex && texCount) {
-        tmp = append(tmp, "  c *= texture2D(u_tex0, v_tex0);\n");
-    }
-    if (texCount > 1) {
-        tmp = append(tmp, "  c *= texture2D(u_tex1, v_tex1);\n");
-    }
-
-    if (extraMath > 0) {
-        tmp = append(tmp, "  c *= u_0;\n");
-    }
-    if (extraMath > 1) {
-        tmp = append(tmp, "  c += u_1;\n");
-    }
-    if (extraMath > 2) {
-        tmp = append(tmp, "  c *= u_2;\n");
-    }
-    if (extraMath > 3) {
-        tmp = append(tmp, "  c += u_3;\n");
-    }
-
-
-    tmp = append(tmp, gShaderPostfix);
-    tmp[0] = 0;
-
-    //printf("%s", str);
-    return str;
-}
-
 static void setupVA() {
     static const float vtx[] = {
         -1.0f,-1.0f,
@@ -231,8 +169,8 @@
     static const float tex0[] = {
         0.0f,0.0f,
         1.0f,0.0f,
-        1.0f,1.0f,
-        0.0f,1.0f };
+        0.0f,1.0f,
+        1.0f,1.0f };
     static const float tex1[] = {
         1.0f,0.0f,
         1.0f,1.0f,
@@ -261,8 +199,8 @@
     }
 }
 
-static void doLoop(bool clear, int pgm, uint32_t w, uint32_t h, const char *str) {
-    if (clear) {
+static void doLoop(bool warmup, int pgm, uint32_t passCount) {
+    if (warmup) {
         glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
         ptSwap();
@@ -272,7 +210,10 @@
 
     startTimer();
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
-    for (int ct=0; ct < 100; ct++) {
+    for (uint32_t ct=0; ct < passCount; ct++) {
+        int loc = glGetUniformLocation(pgm, "u_texOff");
+        glUniform2f(loc, ((float)ct) / passCount, ((float)ct) / 2.f / passCount);
+
         randUniform(pgm, "u_color");
         randUniform(pgm, "u_0");
         randUniform(pgm, "u_1");
@@ -282,14 +223,24 @@
     }
     ptSwap();
     glFinish();
-    endTimer(str, w, h, 1, 100);
+    endTimer(passCount);
+}
+
+
+static uint32_t rgb(uint32_t r, uint32_t g, uint32_t b)
+{
+    uint32_t ret = 0xff000000;
+    ret |= r & 0xff;
+    ret |= (g & 0xff) << 8;
+    ret |= (b & 0xff) << 16;
+    return ret;
 }
 
 void genTextures() {
     uint32_t *m = (uint32_t *)malloc(1024*1024*4);
     for (int y=0; y < 1024; y++){
         for (int x=0; x < 1024; x++){
-            m[y*1024 + x] = 0xff0000ff | ((x & 0xff) << 8) | (y << 16);
+            m[y*1024 + x] = rgb(x, (((x+y) & 0xff) == 0x7f) * 0xff, y);
         }
     }
     glBindTexture(GL_TEXTURE_2D, 1);
@@ -301,7 +252,7 @@
 
     for (int y=0; y < 16; y++){
         for (int x=0; x < 16; x++){
-            m[y*16 + x] = 0xff0000ff | (x<<12) | (y<<20);
+            m[y*16 + x] = rgb(x << 4, (((x+y) & 0xf) == 0x7) * 0xff, y << 4);
         }
     }
     glBindTexture(GL_TEXTURE_2D, 2);
@@ -310,7 +261,38 @@
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-
+    free(m);
 }
 
+static void doSingleTest(uint32_t pgmNum, int tex) {
+    const char *pgmTxt = gFragmentTests[pgmNum]->txt;
+    int pgm = createProgram(gVertexShader, pgmTxt);
+    if (!pgm) {
+        printf("error running test\n");
+        return;
+    }
+    int loc = glGetUniformLocation(pgm, "u_tex0");
+    if (loc >= 0) glUniform1i(loc, 0);
+    loc = glGetUniformLocation(pgm, "u_tex1");
+    if (loc >= 0) glUniform1i(loc, 1);
+
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glActiveTexture(GL_TEXTURE0);
+
+    glBlendFunc(GL_ONE, GL_ONE);
+    glDisable(GL_BLEND);
+    //sprintf(str2, "%i, %i, %i, %i, %i, 0",
+            //useVarColor, texCount, modulateFirstTex, extraMath, tex0);
+    //doLoop(true, pgm, w, h, str2);
+    //doLoop(false, pgm, w, h, str2);
+
+    glEnable(GL_BLEND);
+    sprintf(gCurrentTestName, "%s, %i, %i, 1", gFragmentTests[pgmNum]->name, pgmNum, tex);
+    doLoop(true, pgm, 100);
+    doLoop(false, pgm, 100);
+}
 
diff --git a/opengl/tests/gl_perf/filltest.cpp b/opengl/tests/gl_perf/filltest.cpp
index 0dd4e22..3f8faca 100644
--- a/opengl/tests/gl_perf/filltest.cpp
+++ b/opengl/tests/gl_perf/filltest.cpp
@@ -33,65 +33,19 @@
 
 #include "fill_common.cpp"
 
-static void doSingleTest(uint32_t w, uint32_t h,
-                         bool useVarColor,
-                         int texCount,
-                         bool modulateFirstTex,
-                         int extraMath,
-                         int tex0, int tex1) {
-    char *pgmTxt = genShader(useVarColor, texCount, modulateFirstTex, extraMath);
-    int pgm = createProgram(gVertexShader, pgmTxt);
-    if (!pgm) {
-        printf("error running test\n");
-        return;
-    }
-    int loc = glGetUniformLocation(pgm, "u_tex0");
-    if (loc >= 0) glUniform1i(loc, 0);
-    loc = glGetUniformLocation(pgm, "u_tex1");
-    if (loc >= 0) glUniform1i(loc, 1);
-
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, tex0);
-    glActiveTexture(GL_TEXTURE1);
-    glBindTexture(GL_TEXTURE_2D, tex1);
-    glActiveTexture(GL_TEXTURE0);
-
-    char str2[1024];
-
-    glBlendFunc(GL_ONE, GL_ONE);
-    glDisable(GL_BLEND);
-    //sprintf(str2, "%i, %i, %i, %i, %i, 0",
-            //useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-    //doLoop(true, pgm, w, h, str2);
-    //doLoop(false, pgm, w, h, str2);
-
-    glEnable(GL_BLEND);
-    sprintf(str2, "%i, %i, %i, %i, %i, 1",
-            useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-    doLoop(true, pgm, w, h, str2);
-    doLoop(false, pgm, w, h, str2);
-}
 
 bool doTest(uint32_t w, uint32_t h) {
+    gWidth = w;
+    gHeight = h;
     setupVA();
     genTextures();
 
     printf("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
 
-    for (int texCount = 0; texCount < 2; texCount++) {
-        for (int extraMath = 0; extraMath < 5; extraMath++) {
-
-            doSingleTest(w, h, false, texCount, false, extraMath, 1, 1);
-            doSingleTest(w, h, true, texCount, false, extraMath, 1, 1);
-            if (texCount) {
-                doSingleTest(w, h, false, texCount, true, extraMath, 1, 1);
-                doSingleTest(w, h, true, texCount, true, extraMath, 1, 1);
-
-                doSingleTest(w, h, false, texCount, false, extraMath, 2, 2);
-                doSingleTest(w, h, true, texCount, false, extraMath, 2, 2);
-                doSingleTest(w, h, false, texCount, true, extraMath, 2, 2);
-                doSingleTest(w, h, true, texCount, true, extraMath, 2, 2);
-            }
+    for (uint32_t num = 0; num < gFragmentTestCount; num++) {
+        doSingleTest(num, 2);
+        if (gFragmentTests[num]->texCount) {
+            doSingleTest(num, 1);
         }
     }
 
diff --git a/opengl/tests/gl_perf/fragment_shaders.cpp b/opengl/tests/gl_perf/fragment_shaders.cpp
new file mode 100644
index 0000000..79d5ead
--- /dev/null
+++ b/opengl/tests/gl_perf/fragment_shaders.cpp
@@ -0,0 +1,139 @@
+
+typedef struct FragmentTestRec {
+	const char * name;
+	uint32_t texCount;
+	const char * txt;
+} FragmentTest;
+
+static FragmentTest fpFill = {
+	"Solid color", 0,
+
+    "precision mediump float;\n"
+    "uniform vec4 u_color;\n"
+    "void main() {\n"
+    "  gl_FragColor = u_color;\n"
+    "}\n"
+};
+
+static FragmentTest fpGradient = {
+	"Solid gradient", 0,
+
+    "precision mediump float;\n"
+    "varying lowp vec4 v_color;\n"
+    "void main() {\n"
+    "  gl_FragColor = v_color;\n"
+    "}\n"
+};
+
+static FragmentTest fpCopyTex = {
+	"Texture copy", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  gl_FragColor = texture2D(u_tex0, v_tex0);\n"
+    "}\n"
+};
+
+static FragmentTest fpCopyTexGamma = {
+	"Texture copy with gamma", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  t.rgb = pow(t.rgb, vec3(1.4, 1.4, 1.4));\n"
+    "  gl_FragColor = t;\n"
+    "}\n"
+};
+
+static FragmentTest fpTexSpec = {
+	"Texture spec", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  float simSpec = dot(gl_FragCoord.xyz, gl_FragCoord.xyz);\n"
+    "  simSpec = pow(clamp(simSpec, 0.1, 1.0), 40.0);\n"
+    "  gl_FragColor = t + vec4(simSpec, simSpec, simSpec, simSpec);\n"
+    "}\n"
+};
+
+static FragmentTest fpDepTex = {
+	"Dependent Lookup", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  t += texture2D(u_tex0, t.xy);\n"
+    "  gl_FragColor = t;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateConstantTex = {
+	"Texture modulate constant", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "uniform vec4 u_color;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= u_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateVaryingTex = {
+	"Texture modulate gradient", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "varying lowp vec4 v_color;\n"
+    "uniform sampler2D u_tex0;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= v_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateVaryingConstantTex = {
+	"Texture modulate gradient constant", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "varying lowp vec4 v_color;\n"
+    "uniform sampler2D u_tex0;\n"
+    "uniform vec4 u_color;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= v_color;\n"
+	"  c *= u_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest *gFragmentTests[] = {
+	&fpFill,
+	&fpGradient,
+	&fpCopyTex,
+	&fpCopyTexGamma,
+   &fpTexSpec,
+   &fpDepTex,
+	&fpModulateConstantTex,
+	&fpModulateVaryingTex,
+	&fpModulateVaryingConstantTex,
+
+};
+
+static const size_t gFragmentTestCount = sizeof(gFragmentTests) / sizeof(gFragmentTests[0]);
diff --git a/opengl/tests/gl_perfapp/jni/gl_code.cpp b/opengl/tests/gl_perfapp/jni/gl_code.cpp
index e643292..f993371 100644
--- a/opengl/tests/gl_perfapp/jni/gl_code.cpp
+++ b/opengl/tests/gl_perfapp/jni/gl_code.cpp
@@ -32,82 +32,17 @@
 
 // Saves the parameters of the test (so we can print them out when we finish the timing.)
 
-char saveBuf[1024];
-
 
 int pgm;
 
 void ptSwap() {
 }
 
-static void doSingleTest(uint32_t w, uint32_t h,
-                         bool useVarColor,
-                         int texCount,
-                         bool modulateFirstTex,
-                         int extraMath,
-                         int tex0, int tex1) {
-    int doSingleTestState = (stateClock / doLoopStates) % doSingleTestStates;
-    // LOGI("doSingleTest %d\n", doSingleTestState);
-    switch (doSingleTestState) {
-	case 0: {
-	    char *pgmTxt = genShader(useVarColor, texCount, modulateFirstTex, extraMath);
-	    pgm = createProgram(gVertexShader, pgmTxt);
-	    if (!pgm) {
-		LOGE("error running test\n");
-		return;
-	    }
-	    int loc = glGetUniformLocation(pgm, "u_tex0");
-	    if (loc >= 0) glUniform1i(loc, 0);
-	    loc = glGetUniformLocation(pgm, "u_tex1");
-	    if (loc >= 0) glUniform1i(loc, 1);
+void doTest() {
+    uint32_t testNum = stateClock >> 2;
+    int texSize = ((stateClock >> 1) & 0x1) + 1;
 
-	    glActiveTexture(GL_TEXTURE0);
-	    glBindTexture(GL_TEXTURE_2D, tex0);
-	    glActiveTexture(GL_TEXTURE1);
-	    glBindTexture(GL_TEXTURE_2D, tex1);
-	    glActiveTexture(GL_TEXTURE0);
-
-
-	    glBlendFunc(GL_ONE, GL_ONE);
-	    glDisable(GL_BLEND);
-            char str2[1024];
-	    sprintf(str2, "%i, %i, %i, %i, %i, 0",
-		    useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-
-    	    doLoop((stateClock % doLoopStates) != 0, pgm, w, h, str2);
-	 }
-         break;
-         case 1: {
-            char str2[1024];
-	    glEnable(GL_BLEND);
-	    sprintf(str2, "%i, %i, %i, %i, %i, 1",
-		    useVarColor, texCount, modulateFirstTex, extraMath, tex0);
-	    doLoop((stateClock % doLoopStates) != 0, pgm, w, h, str2);
-        }
-        break;
-    }
-}
-
-
-void doTest(uint32_t w, uint32_t h) {
-    int testState = stateClock / (doLoopStates * doSingleTestStates);
-    int texCount;
-    int extraMath;
-    int testSubState;
-    const int extraMathCount = 5;
-    const int texCount0SubTestCount = 2;
-    const int texCountNSubTestCount = 8;
-
-    if ( testState < extraMathCount * texCount0SubTestCount) {
-       texCount = 0; // Only 10 tests for texCount 0
-       extraMath = (testState / texCount0SubTestCount) % extraMathCount;
-       testSubState = testState % texCount0SubTestCount;
-    } else {
-       texCount = 1 + (testState - extraMathCount * texCount0SubTestCount) / (extraMathCount * texCountNSubTestCount);
-       extraMath = (testState / texCountNSubTestCount) % extraMathCount;
-       testSubState = testState % texCountNSubTestCount;
-    }
-    if (texCount >= 3) {
+    if (testNum >= gFragmentTestCount) {
        LOGI("done\n");
        if (fOut) {
            fclose(fOut);
@@ -117,36 +52,10 @@
        return;
     }
 
-
     // LOGI("doTest %d %d %d\n", texCount, extraMath, testSubState);
 
-    switch(testSubState) {
-	case 0:
-            doSingleTest(w, h, false, texCount, false, extraMath, 1, 1);
-	break;
-	case 1:
-            doSingleTest(w, h, true, texCount, false, extraMath, 1, 1);
-	break;
-	case 2:
-                doSingleTest(w, h, false, texCount, true, extraMath, 1, 1);
-	break;
-	case 3:
-                doSingleTest(w, h, true, texCount, true, extraMath, 1, 1);
-	break;
-
-	case 4:
-                doSingleTest(w, h, false, texCount, false, extraMath, 2, 2);
-	break;
-	case 5:
-                doSingleTest(w, h, true, texCount, false, extraMath, 2, 2);
-	break;
-	case 6:
-                doSingleTest(w, h, false, texCount, true, extraMath, 2, 2);
-	break;
-	case 7:
-                doSingleTest(w, h, true, texCount, true, extraMath, 2, 2);
-	break;
-    }
+//        for (uint32_t num = 0; num < gFragmentTestCount; num++) {
+    doSingleTest(testNum, texSize);
 }
 
 extern "C" {
@@ -156,27 +65,27 @@
 
 JNIEXPORT void JNICALL Java_com_android_glperf_GLPerfLib_init(JNIEnv * env, jobject obj,  jint width, jint height)
 {
+    gWidth = width;
+    gHeight = height;
     if (!done) {
-	    w = width;
-	    h = height;
-	    stateClock = 0;
-	    done = false;
-	    setupVA();
-	    genTextures();
-	    const char* fileName = "/sdcard/glperf.csv";
+            stateClock = 0;
+            done = false;
+            setupVA();
+            genTextures();
+            const char* fileName = "/sdcard/glperf.csv";
             if (fOut != NULL) {
                  LOGI("Closing partially written output.n");
                  fclose(fOut);
                  fOut = NULL;
             }
-	    LOGI("Writing to: %s\n",fileName);
-	    fOut = fopen(fileName, "w");
-	    if (fOut == NULL) {
-		LOGE("Could not open: %s\n", fileName);
-	    }
+            LOGI("Writing to: %s\n",fileName);
+            fOut = fopen(fileName, "w");
+            if (fOut == NULL) {
+                LOGE("Could not open: %s\n", fileName);
+            }
 
-	    LOGI("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
-	    if (fOut) fprintf(fOut,"varColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\r\n");
+            LOGI("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
+            if (fOut) fprintf(fOut,"varColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\r\n");
     }
 }
 
@@ -184,11 +93,11 @@
 {
     if (! done) {
         if (stateClock > 0 && ((stateClock & 1) == 0)) {
-	    endTimer(saveBuf, w, h, 1, 100);
+            //endTimer(100);
         }
-        doTest(w, h);
+        doTest();
         stateClock++;
     } else {
-	    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+            glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
index fe29dea..b01c5e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
@@ -84,7 +84,7 @@
 
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                Slog.d("KeyButtonView", "press");
+                //Slog.d("KeyButtonView", "press");
                 mDownTime = SystemClock.uptimeMillis();
                 mRepeat = 0;
                 mSending = true;
@@ -132,7 +132,7 @@
         final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, mRepeat,
                 0, 0, 0, flags, InputDevice.SOURCE_KEYBOARD);
         try {
-            Slog.d(StatusBarService.TAG, "injecting event " + ev);
+            //Slog.d(StatusBarService.TAG, "injecting event " + ev);
             mWindowManager.injectInputEventNoWait(ev);
         } catch (RemoteException ex) {
             // System process is dead
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
index e828f68..c5688e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
@@ -36,6 +36,7 @@
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
+import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
@@ -91,6 +92,8 @@
 
     private static final int AM_PM_STYLE = AM_PM_STYLE_GONE;
 
+    private static final int INET_CONDITION_THRESHOLD = 50;
+
     private final Context mContext;
     private final StatusBarManager mService;
     private final Handler mHandler = new StatusBarHandler();
@@ -232,42 +235,62 @@
     };
 
     //***** Data connection icons
-    private int[] mDataIconList = sDataNetType_g;
+    private int[] mDataIconList = sDataNetType_g[0];
     //GSM/UMTS
-    private static final int[] sDataNetType_g = new int[] {
-            R.drawable.stat_sys_data_connected_g,
-            R.drawable.stat_sys_data_in_g,
-            R.drawable.stat_sys_data_out_g,
-            R.drawable.stat_sys_data_inandout_g,
+    private static final int[][] sDataNetType_g = {
+            { R.drawable.stat_sys_data_connected_g,
+              R.drawable.stat_sys_data_in_g,
+              R.drawable.stat_sys_data_out_g,
+              R.drawable.stat_sys_data_inandout_g },
+            { R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0 }
         };
-    private static final int[] sDataNetType_3g = new int[] {
-            R.drawable.stat_sys_data_connected_3g,
-            R.drawable.stat_sys_data_in_3g,
-            R.drawable.stat_sys_data_out_3g,
-            R.drawable.stat_sys_data_inandout_3g,
+    private static final int[][] sDataNetType_3g = {
+            { R.drawable.stat_sys_data_connected_3g,
+              R.drawable.stat_sys_data_in_3g,
+              R.drawable.stat_sys_data_out_3g,
+              R.drawable.stat_sys_data_inandout_3g },
+            { R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0 }
         };
-    private static final int[] sDataNetType_e = new int[] {
-            R.drawable.stat_sys_data_connected_e,
-            R.drawable.stat_sys_data_in_e,
-            R.drawable.stat_sys_data_out_e,
-            R.drawable.stat_sys_data_inandout_e,
+    private static final int[][] sDataNetType_e = {
+            { R.drawable.stat_sys_data_connected_e,
+              R.drawable.stat_sys_data_in_e,
+              R.drawable.stat_sys_data_out_e,
+              R.drawable.stat_sys_data_inandout_e },
+            { R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0 }
         };
     //3.5G
-    private static final int[] sDataNetType_h = new int[] {
-            R.drawable.stat_sys_data_connected_h,
-            R.drawable.stat_sys_data_in_h,
-            R.drawable.stat_sys_data_out_h,
-            R.drawable.stat_sys_data_inandout_h,
+    private static final int[][] sDataNetType_h = {
+            { R.drawable.stat_sys_data_connected_h,
+              R.drawable.stat_sys_data_in_h,
+              R.drawable.stat_sys_data_out_h,
+              R.drawable.stat_sys_data_inandout_h },
+            { R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0 }
     };
 
     //CDMA
     // Use 3G icons for EVDO data and 1x icons for 1XRTT data
-    private static final int[] sDataNetType_1x = new int[] {
-        R.drawable.stat_sys_data_connected_1x,
-        R.drawable.stat_sys_data_in_1x,
-        R.drawable.stat_sys_data_out_1x,
-        R.drawable.stat_sys_data_inandout_1x,
-    };
+    private static final int[][] sDataNetType_1x = {
+            { R.drawable.stat_sys_data_connected_1x,
+              R.drawable.stat_sys_data_in_1x,
+              R.drawable.stat_sys_data_out_1x,
+              R.drawable.stat_sys_data_inandout_1x },
+            { R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0,
+              R.drawable.stat_sys_roaming_cdma_0 }
+            };
 
     // Assume it's all good unless we hear otherwise.  We don't always seem
     // to get broadcasts that it *is* there.
@@ -292,17 +315,22 @@
     private boolean mBluetoothEnabled;
 
     // wifi
-    private static final int[] sWifiSignalImages = new int[] {
-            R.drawable.stat_sys_wifi_signal_1,
-            R.drawable.stat_sys_wifi_signal_2,
-            R.drawable.stat_sys_wifi_signal_3,
-            R.drawable.stat_sys_wifi_signal_4,
+    private static final int[][] sWifiSignalImages = {
+            { R.drawable.stat_sys_wifi_signal_1,
+              R.drawable.stat_sys_wifi_signal_2,
+              R.drawable.stat_sys_wifi_signal_3,
+              R.drawable.stat_sys_wifi_signal_4 },
+            { R.drawable.stat_sys_data_in_e,
+              R.drawable.stat_sys_data_in_e,
+              R.drawable.stat_sys_data_in_e,
+              R.drawable.stat_sys_data_in_e }
         };
     private static final int sWifiTemporarilyNotConnectedImage =
             R.drawable.stat_sys_wifi_signal_0;
 
     private int mLastWifiSignalLevel = -1;
     private boolean mIsWifiConnected = false;
+    private int mLastWifiInetConnectivityState = 0;
 
     // sync state
     // If sync is active the SyncActive icon is displayed. If sync is not active but
@@ -353,6 +381,10 @@
             else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
                 updateTTY(intent);
             }
+            else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                // TODO - stop using other means to get wifi/mobile info
+                updateConnectivity(intent);
+            }
         }
     };
 
@@ -389,7 +421,7 @@
         mService.setIconVisibility("data_connection", false);
 
         // wifi
-        mService.setIcon("wifi", sWifiSignalImages[0], 0);
+        mService.setIcon("wifi", sWifiSignalImages[0][0], 0);
         mService.setIconVisibility("wifi", false);
         // wifi will get updated by the sticky intents
 
@@ -456,6 +488,7 @@
         filter.addAction(LocationManager.GPS_FIX_CHANGE_ACTION);
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
 
         // load config to determine if to distinguish Hspa data icon
@@ -659,6 +692,50 @@
         }
     }
 
+    private void updateConnectivity(Intent intent) {
+        NetworkInfo info = (NetworkInfo)(intent.getParcelableExtra(
+                ConnectivityManager.EXTRA_NETWORK_INFO));
+        int connectionStatus = intent.getIntExtra(ConnectivityManager.EXTRA_INET_CONDITION, 0);
+        Slog.d(TAG, "got CONNECTIVITY_ACTION - info=" + info + ", status = " + connectionStatus);
+        if (info.isConnected() == false) return;
+
+        switch (info.getType()) {
+        case ConnectivityManager.TYPE_MOBILE:
+            if (info.isConnected()) {
+                updateDataNetType(info.getSubtype(), connectionStatus);
+                updateDataIcon();
+            }
+            break;
+        case ConnectivityManager.TYPE_WIFI:
+            if (info.isConnected()) {
+                mIsWifiConnected = true;
+                mLastWifiInetConnectivityState =
+                        (connectionStatus > INET_CONDITION_THRESHOLD ? 1 : 0);
+                int iconId;
+                if (mLastWifiSignalLevel == -1) {
+                    iconId = sWifiSignalImages[mLastWifiInetConnectivityState][0];
+                } else {
+                    iconId = sWifiSignalImages[mLastWifiInetConnectivityState]
+                            [mLastWifiSignalLevel];
+                }
+
+                mService.setIcon("wifi", iconId, 0);
+                // Show the icon since wi-fi is connected
+                mService.setIconVisibility("wifi", true);
+            } else {
+                mLastWifiSignalLevel = -1;
+                mIsWifiConnected = false;
+                mLastWifiInetConnectivityState = 0;
+                int iconId = sWifiSignalImages[0][0];
+
+                mService.setIcon("wifi", iconId, 0);
+                // Hide the icon since we're not connected
+                mService.setIconVisibility("wifi", false);
+            }
+            break;
+        }
+    }
+
     private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         @Override
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
@@ -686,7 +763,7 @@
         @Override
         public void onDataConnectionStateChanged(int state, int networkType) {
             mDataState = state;
-            updateDataNetType(networkType);
+            updateDataNetType(networkType, 0);
             updateDataIcon();
         }
 
@@ -848,37 +925,38 @@
         return (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr;
     }
 
-    private final void updateDataNetType(int net) {
+    private final void updateDataNetType(int net, int inetCondition) {
+        int connected = (inetCondition > INET_CONDITION_THRESHOLD ? 1 : 0);
         switch (net) {
         case TelephonyManager.NETWORK_TYPE_EDGE:
-            mDataIconList = sDataNetType_e;
+            mDataIconList = sDataNetType_e[connected];
             break;
         case TelephonyManager.NETWORK_TYPE_UMTS:
-            mDataIconList = sDataNetType_3g;
+            mDataIconList = sDataNetType_3g[connected];
             break;
         case TelephonyManager.NETWORK_TYPE_HSDPA:
         case TelephonyManager.NETWORK_TYPE_HSUPA:
         case TelephonyManager.NETWORK_TYPE_HSPA:
             if (mHspaDataDistinguishable) {
-                mDataIconList = sDataNetType_h;
+                mDataIconList = sDataNetType_h[connected];
             } else {
-                mDataIconList = sDataNetType_3g;
+                mDataIconList = sDataNetType_3g[connected];
             }
             break;
         case TelephonyManager.NETWORK_TYPE_CDMA:
             // display 1xRTT for IS95A/B
-            mDataIconList = this.sDataNetType_1x;
+            mDataIconList = sDataNetType_1x[connected];
             break;
         case TelephonyManager.NETWORK_TYPE_1xRTT:
-            mDataIconList = this.sDataNetType_1x;
+            mDataIconList = sDataNetType_1x[connected];
             break;
         case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through
         case TelephonyManager.NETWORK_TYPE_EVDO_A:
         case TelephonyManager.NETWORK_TYPE_EVDO_B:
-            mDataIconList = sDataNetType_3g;
+            mDataIconList = sDataNetType_3g[connected];
             break;
         default:
-            mDataIconList = sDataNetType_g;
+            mDataIconList = sDataNetType_g[connected];
         break;
         }
     }
@@ -1019,34 +1097,6 @@
             if (!enabled) {
                 mService.setIconVisibility("wifi", false);
             }
-        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-
-            final NetworkInfo networkInfo = (NetworkInfo)
-                    intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
-
-            int iconId;
-            if (networkInfo != null && networkInfo.isConnected()) {
-                mIsWifiConnected = true;
-                if (mLastWifiSignalLevel == -1) {
-                    iconId = sWifiSignalImages[0];
-                } else {
-                    iconId = sWifiSignalImages[mLastWifiSignalLevel];
-                }
-
-                mService.setIcon("wifi", iconId, 0);
-                // Show the icon since wi-fi is connected
-                mService.setIconVisibility("wifi", true);
-
-            } else {
-                mLastWifiSignalLevel = -1;
-                mIsWifiConnected = false;
-                iconId = sWifiSignalImages[0];
-
-                mService.setIcon("wifi", iconId, 0);
-                // Hide the icon since we're not connected
-                mService.setIconVisibility("wifi", false);
-            }
-
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
             int iconId;
             final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
@@ -1055,7 +1105,7 @@
             if (newSignalLevel != mLastWifiSignalLevel) {
                 mLastWifiSignalLevel = newSignalLevel;
                 if (mIsWifiConnected) {
-                    iconId = sWifiSignalImages[newSignalLevel];
+                    iconId = sWifiSignalImages[mLastWifiInetConnectivityState][newSignalLevel];
                 } else {
                     iconId = sWifiTemporarilyNotConnectedImage;
                 }
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index d1fbf17..e5889bf 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -567,16 +567,10 @@
     if (checkPidAndHardware() != NO_ERROR) return;
 
     mPreviewCallbackFlag = callback_flag;
-
-    // If we don't use overlay, we always need the preview frame for display.
-    // If we do use overlay, we only need the preview frame if the user
-    // wants the data.
-    if (mUseOverlay) {
-        if(mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) {
-            enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-        } else {
-            disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-        }
+    if (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) {
+        enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
+    } else {
+        disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
     }
 }
 
@@ -636,7 +630,6 @@
     } else {
         // XXX: Set the orientation of the ANativeWindow.
         mHardware->setPreviewWindow(mPreviewWindow);
-        enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
         result = mHardware->startPreview();
     }
     return result;
@@ -1025,9 +1018,7 @@
         mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK |
                                   FRAME_CALLBACK_FLAG_COPY_OUT_MASK |
                                   FRAME_CALLBACK_FLAG_ENABLE_MASK);
-        if (mUseOverlay) {
-            disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-        }
+        disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
     }
 
     if (c != 0) {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 3db5dc1..e454c08 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -668,7 +668,7 @@
                     while (true) {
                         String packageName = in.readUTF();
                         Slog.i(TAG, "    + " + packageName);
-                        dataChanged(packageName);
+                        dataChangedImpl(packageName);
                     }
                 } catch (EOFException e) {
                     // no more data; we're done
@@ -740,7 +740,7 @@
                 int uid = mBackupParticipants.keyAt(i);
                 HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
                 for (ApplicationInfo app: participants) {
-                    dataChanged(app.packageName);
+                    dataChangedImpl(app.packageName);
                 }
             }
         }
@@ -896,7 +896,7 @@
                 if (!mEverStoredApps.contains(pkg.packageName)) {
                     if (DEBUG) Slog.i(TAG, "New app " + pkg.packageName
                             + " never backed up; scheduling");
-                    dataChanged(pkg.packageName);
+                    dataChangedImpl(pkg.packageName);
                 }
             }
         }
@@ -1327,7 +1327,7 @@
                 if (status != BackupConstants.TRANSPORT_OK) {
                     Slog.w(TAG, "Backup pass unsuccessful, restaging");
                     for (BackupRequest req : mQueue) {
-                        dataChanged(req.appInfo.packageName);
+                        dataChangedImpl(req.appInfo.packageName);
                     }
 
                     // We also want to reset the backup schedule based on whatever
@@ -1997,25 +1997,66 @@
         }
     }
 
+    private void dataChangedImpl(String packageName) {
+        HashSet<ApplicationInfo> targets = dataChangedTargets(packageName);
+        dataChangedImpl(packageName, targets);
+    }
 
-    // ----- IBackupManager binder interface -----
-
-    public void dataChanged(String packageName) {
+    private void dataChangedImpl(String packageName, HashSet<ApplicationInfo> targets) {
         // Record that we need a backup pass for the caller.  Since multiple callers
         // may share a uid, we need to note all candidates within that uid and schedule
         // a backup pass for each of them.
         EventLog.writeEvent(EventLogTags.BACKUP_DATA_CHANGED, packageName);
 
+        if (targets == null) {
+            Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
+                   + " uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mQueueLock) {
+            // Note that this client has made data changes that need to be backed up
+            for (ApplicationInfo app : targets) {
+                // validate the caller-supplied package name against the known set of
+                // packages associated with this uid
+                if (app.packageName.equals(packageName)) {
+                    // Add the caller to the set of pending backups.  If there is
+                    // one already there, then overwrite it, but no harm done.
+                    BackupRequest req = new BackupRequest(app, false);
+                    if (mPendingBackups.put(app, req) == null) {
+                        // Journal this request in case of crash.  The put()
+                        // operation returned null when this package was not already
+                        // in the set; we want to avoid touching the disk redundantly.
+                        writeToJournalLocked(packageName);
+
+                        if (DEBUG) {
+                            int numKeys = mPendingBackups.size();
+                            Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
+                            for (BackupRequest b : mPendingBackups.values()) {
+                                Slog.d(TAG, "    + " + b + " agent=" + b.appInfo.backupAgentName);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // Note: packageName is currently unused, but may be in the future
+    private HashSet<ApplicationInfo> dataChangedTargets(String packageName) {
         // If the caller does not hold the BACKUP permission, it can only request a
         // backup of its own data.
-        HashSet<ApplicationInfo> targets;
         if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
                 Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
-            targets = mBackupParticipants.get(Binder.getCallingUid());
-        } else {
-            // a caller with full permission can ask to back up any participating app
-            // !!! TODO: allow backup of ANY app?
-            targets = new HashSet<ApplicationInfo>();
+            synchronized (mBackupParticipants) {
+                return mBackupParticipants.get(Binder.getCallingUid());
+            }
+        }
+
+        // a caller with full permission can ask to back up any participating app
+        // !!! TODO: allow backup of ANY app?
+        HashSet<ApplicationInfo> targets = new HashSet<ApplicationInfo>();
+        synchronized (mBackupParticipants) {
             int N = mBackupParticipants.size();
             for (int i = 0; i < N; i++) {
                 HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i);
@@ -2024,37 +2065,7 @@
                 }
             }
         }
-        if (targets != null) {
-            synchronized (mQueueLock) {
-                // Note that this client has made data changes that need to be backed up
-                for (ApplicationInfo app : targets) {
-                    // validate the caller-supplied package name against the known set of
-                    // packages associated with this uid
-                    if (app.packageName.equals(packageName)) {
-                        // Add the caller to the set of pending backups.  If there is
-                        // one already there, then overwrite it, but no harm done.
-                        BackupRequest req = new BackupRequest(app, false);
-                        if (mPendingBackups.put(app, req) == null) {
-                            // Journal this request in case of crash.  The put()
-                            // operation returned null when this package was not already
-                            // in the set; we want to avoid touching the disk redundantly.
-                            writeToJournalLocked(packageName);
-
-                            if (DEBUG) {
-                                int numKeys = mPendingBackups.size();
-                                Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
-                                for (BackupRequest b : mPendingBackups.values()) {
-                                    Slog.d(TAG, "    + " + b + " agent=" + b.appInfo.backupAgentName);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        } else {
-            Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
-                    + " uid=" + Binder.getCallingUid());
-        }
+        return targets;
     }
 
     private void writeToJournalLocked(String str) {
@@ -2072,6 +2083,23 @@
         }
     }
 
+    // ----- IBackupManager binder interface -----
+
+    public void dataChanged(final String packageName) {
+        final HashSet<ApplicationInfo> targets = dataChangedTargets(packageName);
+        if (targets == null) {
+            Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
+                   + " uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        mBackupHandler.post(new Runnable() {
+                public void run() {
+                    dataChangedImpl(packageName, targets);
+                }
+            });
+    }
+
     // Clear the given package's backup data from the current transport
     public void clearBackupData(String packageName) {
         if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index b6d725f..96a239d 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -102,6 +102,11 @@
     private Context mContext;
     private int mNetworkPreference;
     private int mActiveDefaultNetwork = -1;
+    // 0 is full bad, 100 is full good
+    private int mDefaultInetCondition = 0;
+    private int mDefaultInetConditionPublished = 0;
+    private boolean mInetConditionChangeInFlight = false;
+    private int mDefaultConnectionSequence = 0;
 
     private int mNumDnsEntries;
 
@@ -1077,6 +1082,7 @@
             intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
                     info.getExtraInfo());
         }
+        intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
         sendStickyBroadcast(intent);
     }
 
@@ -1203,6 +1209,14 @@
                 }
             }
             mActiveDefaultNetwork = type;
+            // this will cause us to come up initially as unconnected and switching
+            // to connected after our normal pause unless somebody reports us as reall
+            // disconnected
+            mDefaultInetConditionPublished = 0;
+            mDefaultConnectionSequence++;
+            mInetConditionChangeInFlight = false;
+            // Don't do this - if we never sign in stay, grey
+            //reportNetworkCondition(mActiveDefaultNetwork, 100);
         }
         thisNet.setTeardownRequested(false);
         updateNetworkSettings(thisNet);
@@ -1637,6 +1651,70 @@
                                 causedBy + " released by timeout");
                     }
                     break;
+                case NetworkStateTracker.EVENT_INET_CONDITION_CHANGE:
+                    if (DBG) {
+                        Slog.d(TAG, "Inet connectivity change, net=" +
+                                msg.arg1 + ", condition=" + msg.arg2 +
+                                ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
+                    }
+                    if (mActiveDefaultNetwork == -1) {
+                        if (DBG) Slog.d(TAG, "no active default network - aborting");
+                        break;
+                    }
+                    if (mActiveDefaultNetwork != msg.arg1) {
+                        if (DBG) Slog.d(TAG, "given net not default - aborting");
+                        break;
+                    }
+                    mDefaultInetCondition = msg.arg2;
+                    int delay;
+                    if (mInetConditionChangeInFlight == false) {
+                        if (DBG) Slog.d(TAG, "starting a change hold");
+                        // setup a new hold to debounce this
+                        if (mDefaultInetCondition > 50) {
+                            delay = Settings.Secure.getInt(mContext.getContentResolver(),
+                                    Settings.Secure.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
+                        } else {
+                            delay = Settings.Secure.getInt(mContext.getContentResolver(),
+                                    Settings.Secure.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
+                        }
+                        mInetConditionChangeInFlight = true;
+                        sendMessageDelayed(obtainMessage(
+                                NetworkStateTracker.EVENT_INET_CONDITION_HOLD_END,
+                                mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
+                    } else {
+                        // we've set the new condition, when this hold ends that will get
+                        // picked up
+                        if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt");
+                    }
+                    break;
+                case NetworkStateTracker.EVENT_INET_CONDITION_HOLD_END:
+                    if (DBG) {
+                        Slog.d(TAG, "Inet hold end, net=" + msg.arg1 +
+                                ", condition =" + mDefaultInetCondition +
+                                ", published condition =" + mDefaultInetConditionPublished);
+                    }
+                    mInetConditionChangeInFlight = false;
+
+                    if (mActiveDefaultNetwork == -1) {
+                        if (DBG) Slog.d(TAG, "no active default network - aborting");
+                        break;
+                    }
+                    if (mDefaultConnectionSequence != msg.arg2) {
+                        if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting");
+                        break;
+                    }
+                    if (mDefaultInetConditionPublished == mDefaultInetCondition) {
+                        if (DBG) Slog.d(TAG, "no change in condition - aborting");
+                        break;
+                    }
+                    NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+                    if (networkInfo.isConnected() == false) {
+                        if (DBG) Slog.d(TAG, "default network not connected - aborting");
+                        break;
+                    }
+                    mDefaultInetConditionPublished = mDefaultInetCondition;
+                    sendConnectedBroadcast(networkInfo);
+                    break;
             }
         }
     }
@@ -1748,4 +1826,15 @@
                 mNetTransitionWakeLockTimeout);
         return;
     }
+
+    // 100 percent is full good, 0 is full bad.
+    public void reportInetCondition(int networkType, int percentage) {
+        if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")");
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.STATUS_BAR,
+                "ConnectivityService");
+
+        mHandler.sendMessage(mHandler.obtainMessage(
+            NetworkStateTracker.EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+    }
 }
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 9d262b6..5afabbd 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -113,6 +113,10 @@
     private boolean mScreenOn = true;
     private boolean mInCall = false;
     private boolean mNotificationPulseEnabled;
+    // This is true if we have received a new notification while the screen is off
+    // (that is, if mLedNotification was set while the screen was off)
+    // This is reset to false when the screen is turned on.
+    private boolean mPendingPulseNotification;
 
     // for adb connected notifications
     private boolean mAdbNotificationShown = false;
@@ -1098,6 +1102,11 @@
             mBatteryLight.turnOff();
         }
 
+        // clear pending pulse notification if screen is on
+        if (mScreenOn || mLedNotification == null) {
+            mPendingPulseNotification = false;
+        }
+
         // handle notification lights
         if (mLedNotification == null) {
             // get next notification, if any
@@ -1105,11 +1114,14 @@
             if (n > 0) {
                 mLedNotification = mLights.get(n-1);
             }
+            if (mLedNotification != null && !mScreenOn) {
+                mPendingPulseNotification = true;
+            }
         }
 
         // we only flash if screen is off and persistent pulsing is enabled
         // and we are not currently in a call
-        if (mLedNotification == null || mScreenOn || mInCall) {
+        if (!mPendingPulseNotification || mScreenOn || mInCall) {
             mNotificationLight.turnOff();
         } else {
             int ledARGB = mLedNotification.notification.ledARGB;
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 5ae87cf..f6d92b5 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -9408,7 +9408,12 @@
         } catch (NoSuchAlgorithmException nsae) {
             Slog.e(TAG, "Failed to create encryption keys with exception: " + nsae);
             return null;
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Failed to retrieve encryption keys with exception: "
+                      + ioe);
+            return null;
         }
+
     }
 
     /* package */ static String getTempContainerId() {
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index 563ce58..eee97c3 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -490,7 +490,7 @@
 
     private class KeepAliveProcess implements Runnable {
         private static final String TAG = "\\KEEPALIVE/";
-        private static final int INTERVAL = 15;
+        private static final int INTERVAL = 10;
         private SipSessionGroup.SipSessionImpl mSession;
 
         public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 166c528..3e23929 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -292,6 +292,9 @@
 void DisplayHardware::releaseScreen() const
 {
     DisplayHardwareBase::releaseScreen();
+    if (mHwc->initCheck() == NO_ERROR) {
+        mHwc->release();
+    }
 }
 
 void DisplayHardware::acquireScreen() const
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 0291d78..129be4e 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -87,6 +87,11 @@
     return (status_t)err;
 }
 
+status_t HWComposer::release() const {
+    int err = mHwc->set(mHwc, NULL, NULL, NULL);
+    return (status_t)err;
+}
+
 size_t HWComposer::getNumLayers() const {
     return mList ? mList->numHwLayers : 0;
 }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index c5d5c2b..22ff10c 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -48,6 +48,8 @@
     // commits the list
     status_t commit() const;
 
+    // release hardware resources
+    status_t release() const;
 
     size_t getNumLayers() const;
     hwc_layer_t* getLayers() const;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a78d9b9..d820380 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -383,10 +383,10 @@
         // inform the h/w that we're done compositing
         hw.compositionComplete();
 
-        // release the clients before we flip ('cause flip might block)
+        postFramebuffer();
+
         unlockClients();
 
-        postFramebuffer();
     } else {
         // pretend we did the post
         unlockClients();
@@ -885,8 +885,8 @@
      */
     for (size_t i=0 ; i<count ; i++) {
         if (cur) {
-            if (!(cur[i].compositionType == HWC_FRAMEBUFFER) ||
-                    cur[i].flags & HWC_SKIP_LAYER) {
+            if ((cur[i].compositionType != HWC_FRAMEBUFFER) &&
+                !(cur[i].flags & HWC_SKIP_LAYER)) {
                 // skip layers handled by the HAL
                 continue;
             }
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index d899430..0746562 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -53,6 +53,10 @@
     public static final int ENCODING_7BIT = 1;
     public static final int ENCODING_8BIT = 2;
     public static final int ENCODING_16BIT = 3;
+    /**
+     * @hide This value is not defined in global standard. Only in Korea, this is used.
+     */
+    public static final int ENCODING_KSC5601 = 4;
 
     /** The maximum number of payload bytes per message */
     public static final int MAX_USER_DATA_BYTES = 140;
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index c1232e8..caec7e1 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -466,6 +466,33 @@
     }
 
     /**
+     * Hangup foreground call and resume the specific background call
+     *
+     * Note: this is noop if there is no foreground call or the heldCall is null
+     *
+     * @param heldCall to become foreground
+     * @throws CallStateException
+     */
+    public void hangupForegroundResumeBackground(Call heldCall) throws CallStateException {
+        Phone foregroundPhone = null;
+        Phone backgroundPhone = null;
+
+        if (hasActiveFgCall()) {
+            foregroundPhone = getFgPhone();
+            if (heldCall != null) {
+                backgroundPhone = heldCall.getPhone();
+                if (foregroundPhone == backgroundPhone) {
+                    getActiveFgCall().hangup();
+                } else {
+                // the call to be hangup and resumed belongs to different phones
+                    getActiveFgCall().hangup();
+                    switchHoldingAndActive(heldCall);
+                }
+            }
+        }
+    }
+
+    /**
      * Whether or not the phone can conference in the current phone
      * state--that is, one call holding and one call active.
      * @return true if the phone can conference; false otherwise.
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index 75ea116..e42827f 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -16,10 +16,14 @@
 
 package com.android.internal.telephony;
 
+import android.text.TextUtils;
 import android.util.SparseIntArray;
 
 import android.util.Log;
 
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
 /**
  * This class implements the character set mapping between
  * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
@@ -354,6 +358,32 @@
      */
     public static String
     gsm8BitUnpackedToString(byte[] data, int offset, int length) {
+        return gsm8BitUnpackedToString(data, offset, length, "");
+    }
+
+    /**
+     * Convert a GSM alphabet string that's stored in 8-bit unpacked
+     * format (as it often appears in SIM records) into a String
+     *
+     * Field may be padded with trailing 0xff's. The decode stops
+     * at the first 0xff encountered.
+     *
+     * Additionally, in some country(ex. Korea), there are non-ASCII or MBCS characters.
+     * If a character set is given, characters in data are treat as MBCS.
+     */
+    public static String
+    gsm8BitUnpackedToString(byte[] data, int offset, int length, String characterset) {
+        boolean isMbcs = false;
+        Charset charset = null;
+        ByteBuffer mbcsBuffer = null;
+
+        if (!TextUtils.isEmpty(characterset)
+                && !characterset.equalsIgnoreCase("us-ascii")
+                && Charset.isSupported(characterset)) {
+            isMbcs = true;
+            charset = Charset.forName(characterset);
+            mbcsBuffer = ByteBuffer.allocate(2);
+        }
         boolean prevWasEscape;
         StringBuilder ret = new StringBuilder(length);
 
@@ -379,7 +409,15 @@
                 if (prevWasEscape) {
                     ret.append((char)gsmExtendedToChar.get(c, ' '));
                 } else {
-                    ret.append((char)gsmToChar.get(c, ' '));
+                    if (!isMbcs || c < 0x80 || i + 1 >= offset + length) {
+                        ret.append((char)gsmToChar.get(c, ' '));
+                    } else {
+                        // isMbcs must be true. So both mbcsBuffer and charset are initialized.
+                        mbcsBuffer.clear();
+                        mbcsBuffer.put(data, i++, 2);
+                        mbcsBuffer.flip();
+                        ret.append(charset.decode(mbcsBuffer).toString());
+                    }
                 }
                 prevWasEscape = false;
             }
diff --git a/telephony/java/com/android/internal/telephony/IccUtils.java b/telephony/java/com/android/internal/telephony/IccUtils.java
index 005ae37..df579b0 100644
--- a/telephony/java/com/android/internal/telephony/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/IccUtils.java
@@ -16,13 +16,16 @@
 
 package com.android.internal.telephony;
 
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.util.Log;
 
 import com.android.internal.telephony.GsmAlphabet;
-
 import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 
 /**
  * Various methods, useful for dealing with SIM data.
@@ -150,6 +153,9 @@
      */
     public static String
     adnStringFieldToString(byte[] data, int offset, int length) {
+        if (length == 0) {
+            return "";
+        }
         if (length >= 1) {
             if (data[offset] == (byte) 0x80) {
                 int ucslen = (length - 1) / 2;
@@ -225,7 +231,15 @@
             return ret.toString();
         }
 
-        return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length);
+        Resources resource = Resources.getSystem();
+        String defaultCharset = "";
+        try {
+            defaultCharset =
+                    resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
+        } catch (NotFoundException e) {
+            // Ignore Exception and defaultCharset is set to a empty string.
+        }
+        return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
     }
 
     static int
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index a77484a..e24613f 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -33,6 +33,7 @@
 import static android.telephony.SmsMessage.ENCODING_7BIT;
 import static android.telephony.SmsMessage.ENCODING_8BIT;
 import static android.telephony.SmsMessage.ENCODING_16BIT;
+import static android.telephony.SmsMessage.ENCODING_KSC5601;
 import static android.telephony.SmsMessage.ENCODING_UNKNOWN;
 import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
 import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
@@ -776,6 +777,27 @@
             return ret;
         }
 
+        /**
+         * Interprets the user data payload as KSC-5601 characters, and
+         * decodes them into a String.
+         *
+         * @param byteCount the number of bytes in the user data payload
+         * @return a String with the decoded characters
+         */
+        String getUserDataKSC5601(int byteCount) {
+            String ret;
+
+            try {
+                ret = new String(pdu, cur, byteCount, "KSC5601");
+            } catch (UnsupportedEncodingException ex) {
+                ret = "";
+                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+            }
+
+            cur += byteCount;
+            return ret;
+        }
+
         boolean moreDataPresent() {
             return (pdu.length > cur);
         }
@@ -1111,6 +1133,16 @@
                 Log.w(LOG_TAG, "MWI for fax, email, or other "
                         + (dataCodingScheme & 0xff));
             }
+        } else if ((dataCodingScheme & 0xC0) == 0x80) {
+            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+            // 0x80..0xBF == Reserved coding groups
+            if (dataCodingScheme == 0x84) {
+                // This value used for KSC5601 by carriers in Korea.
+                encodingType = ENCODING_KSC5601;
+            } else {
+                Log.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+                        + (dataCodingScheme & 0xff));
+            }
         } else {
             Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
                     + (dataCodingScheme & 0xff));
@@ -1135,6 +1167,10 @@
         case ENCODING_16BIT:
             messageBody = p.getUserDataUCS2(count);
             break;
+
+        case ENCODING_KSC5601:
+            messageBody = p.getUserDataKSC5601(count);
+            break;
         }
 
         if (Config.LOGV) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
index a6b9a2a..7011aeb 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import com.android.internal.telephony.GsmAlphabet;
+
 import junit.framework.TestCase;
 
 import android.test.suitebuilder.annotation.LargeTest;
@@ -307,4 +309,26 @@
         assertEquals("a",
                 GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
     }
+
+    @SmallTest
+    public void testGsm8BitUpackedWithEuckr() throws Exception {
+        // Some feature phones in Korea store contacts as euc-kr.
+        // Test this situations.
+        byte unpacked[];
+
+        // Test general alphabet strings.
+        unpacked = IccUtils.hexStringToBytes("61626320646566FF");
+        assertEquals("abc def",
+                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+        // Test korean strings.
+        unpacked = IccUtils.hexStringToBytes("C5D7BDBAC6AEFF");
+        assertEquals("\uD14C\uC2A4\uD2B8",
+                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+        // Test gsm Extented Characters.
+        unpacked = GsmAlphabet.stringToGsm8BitPacked(sGsmExtendedChars);
+        assertEquals(sGsmExtendedChars,
+                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+    }
 }
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
index db38ede..ef62d85 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
@@ -28,7 +28,7 @@
     public void testBasic() throws Exception {
         byte[] data, data2;
 
-        /* 
+        /*
          * bcdToString()
          */
 
@@ -40,9 +40,13 @@
         assertEquals("0126045001448486", IccUtils.bcdToString(data, 1, data.length - 2));
 
         // Stops on invalid BCD value
-        data = IccUtils.hexStringToBytes("98F062400510444868f2");
+        data = IccUtils.hexStringToBytes("98E062400510444868f2");
         assertEquals("890", IccUtils.bcdToString(data, 0, data.length));
 
+        // skip the high nibble 'F' since some PLMNs have it
+        data = IccUtils.hexStringToBytes("98F062400510444868f2");
+        assertEquals("890260450014484862", IccUtils.bcdToString(data, 0, data.length));
+
         /*
          * gsmBcdByteToInt()
          */
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index db1e5ef..ec1cbf1 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -230,6 +230,16 @@
         </activity>
 
         <activity
+                android:name="FramebufferBlendActivity"
+                android:label="_FramebufferBlend">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+
+        <activity
                 android:name="StackActivity"
                 android:label="_Stacks">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java
new file mode 100644
index 0000000..ef84b67
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class FramebufferBlendActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new BlendView(this));
+    }
+
+    static class BlendView extends View {
+        private int mTexWidth;
+        private int mTexHeight;
+        private Paint mPaint;
+        private LinearGradient mHorGradient;
+        private Bitmap mTexture;
+
+        BlendView(Context c) {
+            super(c);
+
+            mTexture = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mTexWidth = mTexture.getWidth();
+            mTexHeight = mTexture.getHeight();
+
+            mHorGradient = new LinearGradient(0.0f, 0.0f, mTexWidth, 0.0f,
+                    Color.BLACK, Color.WHITE, Shader.TileMode.CLAMP);
+
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            canvas.save();
+            canvas.translate(40.0f, 40.0f);
+
+            drawBlendedBitmap(canvas, PorterDuff.Mode.DARKEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.LIGHTEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.MULTIPLY);
+
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(40.0f + mTexWidth + 40.0f, 40.0f);
+
+            drawBlendedBitmap(canvas, PorterDuff.Mode.SCREEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.ADD);
+            drawBlendedBitmapInverse(canvas, PorterDuff.Mode.OVERLAY);
+
+            canvas.restore();
+        }
+
+        private void drawBlendedBitmap(Canvas canvas, PorterDuff.Mode mode) {
+            mPaint.setShader(null);
+            mPaint.setXfermode(null);
+            canvas.drawBitmap(mTexture, 0.0f, 0.0f, mPaint);
+
+            mPaint.setShader(mHorGradient);
+            mPaint.setXfermode(new PorterDuffXfermode(mode));
+            canvas.drawRect(0.0f, 0.0f, mTexWidth, mTexHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mTexHeight);
+        }
+
+        private void drawBlendedBitmapInverse(Canvas canvas, PorterDuff.Mode mode) {
+            mPaint.setXfermode(null);
+            mPaint.setShader(mHorGradient);
+            canvas.drawRect(0.0f, 0.0f, mTexWidth, mTexHeight, mPaint);
+
+            mPaint.setXfermode(new PorterDuffXfermode(mode));
+            mPaint.setShader(null);
+            canvas.drawBitmap(mTexture, 0.0f, 0.0f, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mTexHeight);
+        }
+    }
+}
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index bb45a9a..3433dcf 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -588,7 +588,7 @@
     // Give device socket a reasonable timeout and buffer size.
     timeval tv;
     tv.tv_sec = 0;
-    tv.tv_usec = 1000 * sampleCount / sampleRate * 1000;
+    tv.tv_usec = 1000 * sampleCount / sampleRate * 500;
     if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) ||
         setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) ||
         setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) {
@@ -793,7 +793,7 @@
 
             status_t status = mRecord.obtainBuffer(&buffer, 1);
             if (status == NO_ERROR) {
-                int count = (buffer.frameCount < toRead) ?
+                int count = ((int)buffer.frameCount < toRead) ?
                         buffer.frameCount : toRead;
                 memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2);
                 toRead -= count;