Merge "Force wallpaper surface to 565." into gingerbread
diff --git a/api/current.xml b/api/current.xml
index 8ed7d93..1abf681 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -4024,6 +4024,17 @@
  visibility="public"
 >
 </field>
+<field name="filterTouchesWhenObscured"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843460"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="finishOnCloseSystemDialogs"
  type="int"
  transient="false"
@@ -5861,17 +5872,6 @@
  visibility="public"
 >
 </field>
-<field name="kraken_resource_pad61"
- type="int"
- transient="false"
- volatile="false"
- value="16843460"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="kraken_resource_pad7"
  type="int"
  transient="false"
@@ -15838,6 +15838,50 @@
  visibility="public"
 >
 </field>
+<field name="TextAppearance_StatusBar_EventContent"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973927"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TextAppearance_StatusBar_EventContent_Title"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973928"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TextAppearance_StatusBar_Icon"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973926"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TextAppearance_StatusBar_Title"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973925"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="TextAppearance_Theme"
  type="int"
  transient="false"
@@ -17059,50 +17103,6 @@
  visibility="public"
 >
 </field>
-<field name="kraken_resource_pad41"
- type="int"
- transient="false"
- volatile="false"
- value="16973928"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="kraken_resource_pad42"
- type="int"
- transient="false"
- volatile="false"
- value="16973927"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="kraken_resource_pad43"
- type="int"
- transient="false"
- volatile="false"
- value="16973926"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="kraken_resource_pad44"
- type="int"
- transient="false"
- volatile="false"
- value="16973925"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="kraken_resource_pad5"
  type="int"
  transient="false"
@@ -164852,6 +164852,29 @@
 <parameter name="flags" type="int">
 </parameter>
 </method>
+<method name="formatDateRange"
+ return="java.util.Formatter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="formatter" type="java.util.Formatter">
+</parameter>
+<parameter name="startMillis" type="long">
+</parameter>
+<parameter name="endMillis" type="long">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+<parameter name="timeZone" type="java.lang.String">
+</parameter>
+</method>
 <method name="formatDateTime"
  return="java.lang.String"
  abstract="false"
@@ -165345,7 +165368,7 @@
  value="8192"
  static="true"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </field>
@@ -166460,16 +166483,6 @@
 <parameter name="cursorController" type="android.widget.TextView.CursorController">
 </parameter>
 </method>
-<field name="mCursorController"
- type="android.widget.TextView.CursorController"
- transient="false"
- volatile="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-</field>
 </class>
 <class name="BaseKeyListener"
  extends="android.text.method.MetaKeyKeyListener"
@@ -181672,6 +181685,17 @@
  visibility="public"
 >
 </method>
+<method name="getFlags"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getHistoricalEventTime"
  return="long"
  abstract="false"
@@ -182309,6 +182333,8 @@
 </parameter>
 <parameter name="source" type="int">
 </parameter>
+<parameter name="flags" type="int">
+</parameter>
 </method>
 <method name="obtain"
  return="android.view.MotionEvent"
@@ -182765,6 +182791,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_WINDOW_IS_OBSCURED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="MotionEvent.PointerCoords"
  extends="java.lang.Object"
@@ -185487,6 +185524,17 @@
  visibility="public"
 >
 </method>
+<method name="getFilterTouchesWhenObscured"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getFocusables"
  return="java.util.ArrayList&lt;android.view.View&gt;"
  abstract="false"
@@ -186775,6 +186823,19 @@
 <parameter name="canvas" type="android.graphics.Canvas">
 </parameter>
 </method>
+<method name="onFilterTouchEventForSecurity"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="onFinishInflate"
  return="void"
  abstract="false"
@@ -187681,6 +187742,19 @@
 <parameter name="length" type="int">
 </parameter>
 </method>
+<method name="setFilterTouchesWhenObscured"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enabled" type="boolean">
+</parameter>
+</method>
 <method name="setFocusable"
  return="void"
  abstract="false"
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 2e87394..f6f80d1 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -136,7 +136,7 @@
 /* TODO(oam): depending on use case (ecryptfs or dmcrypt)
  * change implementation
  */
-static int disk_free()
+static int64_t disk_free()
 {
     struct statfs sfs;
     if (statfs(PKG_DIR_PREFIX, &sfs) == 0) {
@@ -154,18 +154,18 @@
  * also require that apps constantly modify file metadata even
  * when just reading from the cache, which is pretty awful.
  */
-int free_cache(int free_size)
+int free_cache(int64_t free_size)
 {
     const char *name;
     int dfd, subfd;
     DIR *d;
     struct dirent *de;
-    int avail;
+    int64_t avail;
 
     avail = disk_free();
     if (avail < 0) return -1;
 
-    LOGI("free_cache(%d) avail %d\n", free_size, avail);
+    LOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail);
     if (avail >= free_size) return 0;
 
     /* First try encrypted dir */
@@ -327,10 +327,10 @@
     return 0;
 }
 
-static int stat_size(struct stat *s)
+static int64_t stat_size(struct stat *s)
 {
-    int blksize = s->st_blksize;
-    int size = s->st_size;
+    int64_t blksize = s->st_blksize;
+    int64_t size = s->st_size;
 
     if (blksize) {
             /* round up to filesystem block size */
@@ -340,9 +340,9 @@
     return size;
 }
 
-static int calculate_dir_size(int dfd)
+static int64_t calculate_dir_size(int dfd)
 {
-    int size = 0;
+    int64_t size = 0;
     struct stat s;
     DIR *d;
     struct dirent *de;
@@ -378,7 +378,7 @@
 
 int get_size(const char *pkgname, const char *apkpath,
              const char *fwdlock_apkpath,
-             int *_codesize, int *_datasize, int *_cachesize, int encrypted_fs_flag)
+             int64_t *_codesize, int64_t *_datasize, int64_t *_cachesize, int encrypted_fs_flag)
 {
     DIR *d;
     int dfd;
@@ -386,9 +386,9 @@
     struct stat s;
     char path[PKG_PATH_MAX];
 
-    int codesize = 0;
-    int datasize = 0;
-    int cachesize = 0;
+    int64_t codesize = 0;
+    int64_t datasize = 0;
+    int64_t cachesize = 0;
 
         /* count the source apk as code -- but only if it's not
          * on the /system partition and its not on the sdcard.
@@ -445,7 +445,7 @@
             }
             subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
             if (subfd >= 0) {
-                int size = calculate_dir_size(subfd);
+                int64_t size = calculate_dir_size(subfd);
                 if (!strcmp(name,"lib")) {
                     codesize += size;
                 } else if(!strcmp(name,"cache")) {
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index 882c493..c991845 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -60,7 +60,7 @@
 
 static int do_free_cache(char **arg, char reply[REPLY_MAX]) /* TODO int:free_size */
 {
-    return free_cache(atoi(arg[0])); /* free_size */
+    return free_cache((int64_t)atoll(arg[0])); /* free_size */
 }
 
 static int do_rm_cache(char **arg, char reply[REPLY_MAX])
@@ -75,15 +75,19 @@
 
 static int do_get_size(char **arg, char reply[REPLY_MAX])
 {
-    int codesize = 0;
-    int datasize = 0;
-    int cachesize = 0;
+    int64_t codesize = 0;
+    int64_t datasize = 0;
+    int64_t cachesize = 0;
     int res = 0;
 
         /* pkgdir, apkpath */
     res = get_size(arg[0], arg[1], arg[2], &codesize, &datasize, &cachesize, atoi(arg[3]));
 
-    sprintf(reply,"%d %d %d", codesize, datasize, cachesize);
+    /*
+     * Each int64_t can take up 22 characters printed out. Make sure it
+     * doesn't go over REPLY_MAX in the future.
+     */
+    snprintf(reply, REPLY_MAX, "%" PRId64 " %" PRId64 " %" PRId64, codesize, datasize, cachesize);
     return res;
 }
 
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 8e4adb1..479e4b2 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -19,6 +19,8 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
 #include <sys/stat.h>
 #include <dirent.h>
 #include <unistd.h>
@@ -105,7 +107,7 @@
 int rm_dex(const char *path);
 int protect(char *pkgname, gid_t gid);
 int get_size(const char *pkgname, const char *apkpath, const char *fwdlock_apkpath,
-             int *codesize, int *datasize, int *cachesize, int encrypted_fs_flag);
-int free_cache(int free_size);
+             int64_t *codesize, int64_t *datasize, int64_t *cachesize, int encrypted_fs_flag);
+int free_cache(int64_t free_size);
 int dexopt(const char *apk_path, uid_t uid, int is_public);
 int movefiles();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 879670e..09ef710 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -325,7 +325,7 @@
         }
         throw new RuntimeException("Not supported in system context");
     }
-    
+
     private static File makeBackupFile(File prefsFile) {
         return new File(prefsFile.getPath() + ".bak");
     }
@@ -337,55 +337,54 @@
     @Override
     public SharedPreferences getSharedPreferences(String name, int mode) {
         SharedPreferencesImpl sp;
+        File prefsFile;
+        boolean needInitialLoad = false;
         synchronized (sSharedPrefs) {
             sp = sSharedPrefs.get(name);
-            if (sp != null && !sp.hasFileChanged()) {
-                //Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
+            if (sp != null && !sp.hasFileChangedUnexpectedly()) {
                 return sp;
             }
-        }
-        File f = getSharedPrefsFile(name);
-
-        FileInputStream str = null;
-        File backup = makeBackupFile(f);
-        if (backup.exists()) {
-            f.delete();
-            backup.renameTo(f);
-        }
-
-        // Debugging
-        if (f.exists() && !f.canRead()) {
-            Log.w(TAG, "Attempt to read preferences file " + f + " without permission");
-        }
-
-        Map map = null;
-        if (f.exists() && f.canRead()) {
-            try {
-                str = new FileInputStream(f);
-                map = XmlUtils.readMapXml(str);
-                str.close();
-            } catch (org.xmlpull.v1.XmlPullParserException e) {
-                Log.w(TAG, "getSharedPreferences", e);
-            } catch (FileNotFoundException e) {
-                Log.w(TAG, "getSharedPreferences", e);
-            } catch (IOException e) {
-                Log.w(TAG, "getSharedPreferences", e);
+            prefsFile = getSharedPrefsFile(name);
+            if (sp == null) {
+                sp = new SharedPreferencesImpl(prefsFile, mode, null);
+                sSharedPrefs.put(name, sp);
+                needInitialLoad = true;
             }
         }
 
-        synchronized (sSharedPrefs) {
-            if (sp != null) {
-                //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map);
-                sp.replace(map);
-            } else {
-                sp = sSharedPrefs.get(name);
-                if (sp == null) {
-                    sp = new SharedPreferencesImpl(f, mode, map);
-                    sSharedPrefs.put(name, sp);
+        synchronized (sp) {
+            if (needInitialLoad && sp.isLoaded()) {
+                // lost the race to load; another thread handled it
+                return sp;
+            }
+            File backup = makeBackupFile(prefsFile);
+            if (backup.exists()) {
+                prefsFile.delete();
+                backup.renameTo(prefsFile);
+            }
+
+            // Debugging
+            if (prefsFile.exists() && !prefsFile.canRead()) {
+                Log.w(TAG, "Attempt to read preferences file " + prefsFile + " without permission");
+            }
+
+            Map map = null;
+            if (prefsFile.exists() && prefsFile.canRead()) {
+                try {
+                    FileInputStream str = new FileInputStream(prefsFile);
+                    map = XmlUtils.readMapXml(str);
+                    str.close();
+                } catch (org.xmlpull.v1.XmlPullParserException e) {
+                    Log.w(TAG, "getSharedPreferences", e);
+                } catch (FileNotFoundException e) {
+                    Log.w(TAG, "getSharedPreferences", e);
+                } catch (IOException e) {
+                    Log.w(TAG, "getSharedPreferences", e);
                 }
             }
-            return sp;
+            sp.replace(map);
         }
+        return sp;
     }
 
     private File getPreferencesDir() {
@@ -2712,6 +2711,10 @@
 
     private static final class SharedPreferencesImpl implements SharedPreferences {
 
+        // Lock ordering rules:
+        //  - acquire SharedPreferencesImpl.this before EditorImpl.this
+        //  - acquire mWritingToDiskLock before EditorImpl.this
+
         private final File mFile;
         private final File mBackupFile;
         private final int mMode;
@@ -2719,6 +2722,7 @@
         private Map<String, Object> mMap;  // guarded by 'this'
         private long mTimestamp;  // guarded by 'this'
         private int mDiskWritesInFlight = 0;  // guarded by 'this'
+        private boolean mLoaded = false;  // guarded by 'this'
 
         private final Object mWritingToDiskLock = new Object();
         private static final Object mContent = new Object();
@@ -2729,6 +2733,7 @@
             mFile = file;
             mBackupFile = makeBackupFile(file);
             mMode = mode;
+            mLoaded = initialContents != null;
             mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
             FileStatus stat = new FileStatus();
             if (FileUtils.getFileStatus(file.getPath(), stat)) {
@@ -2737,7 +2742,23 @@
             mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
         }
 
-        public boolean hasFileChanged() {
+        // Has this SharedPreferences ever had values assigned to it?
+        boolean isLoaded() {
+            synchronized (this) {
+                return mLoaded;
+            }
+        }
+
+        // Has the file changed out from under us?  i.e. writes that
+        // we didn't instigate.
+        public boolean hasFileChangedUnexpectedly() {
+            synchronized (this) {
+                if (mDiskWritesInFlight > 0) {
+                    // If we know we caused it, it's not unexpected.
+                    Log.d(TAG, "disk write in flight, not unexpected.");
+                    return false;
+                }
+            }
             FileStatus stat = new FileStatus();
             if (!FileUtils.getFileStatus(mFile.getPath(), stat)) {
                 return true;
@@ -2748,8 +2769,9 @@
         }
 
         public void replace(Map newContents) {
-            if (newContents != null) {
-                synchronized (this) {
+            synchronized (this) {
+                mLoaded = true;
+                if (newContents != null) {
                     mMap = newContents;
                 }
             }
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 4dc88b3..d7a0412 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -3,8 +3,6 @@
 import com.android.internal.view.IInputMethodCallback;
 import com.android.internal.view.IInputMethodSession;
 
-import dalvik.system.PathClassLoader;
-
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -168,17 +166,14 @@
             // If the application does not have (Java) code, then no ClassLoader
             // has been set up for it.  We will need to do our own search for
             // the native code.
-            path = ai.applicationInfo.dataDir + "/lib/" + System.mapLibraryName(libname);
-            if (!(new File(path)).exists()) {
-                path = null;
+            File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,
+                    System.mapLibraryName(libname));
+            if (libraryFile.exists()) {
+                path = libraryFile.getPath();
             }
         }
         
         if (path == null) {
-            path = ((PathClassLoader)getClassLoader()).findLibrary(libname);
-        }
-        
-        if (path == null) {
             throw new IllegalArgumentException("Unable to find native library: " + libname);
         }
         
diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
index 23b1703..213bd31 100644
--- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.app.QueuedWork;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.ParcelFileDescriptor;
@@ -94,7 +95,11 @@
     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) {
         Context context = mContext;
-        
+
+        // If a SharedPreference has an outstanding write in flight,
+        // wait for it to finish flushing to disk.
+        QueuedWork.waitToFinish();
+
         // make filenames for the prefGroups
         String[] prefGroups = mPrefGroups;
         final int N = prefGroups.length;
@@ -123,4 +128,3 @@
         }
     }
 }
-
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2acc4a0..7154aee 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1567,6 +1567,30 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK";
     /**
+     * Broadcast Action:  A sticky broadcast that indicates a memory full
+     * condition on the device. This is intended for activities that want
+     * to be able to fill the data partition completely, leaving only
+     * enough free space to prevent system-wide SQLite failures.
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     *
+     * {@hide}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DEVICE_STORAGE_FULL = "android.intent.action.DEVICE_STORAGE_FULL";
+    /**
+     * Broadcast Action:  Indicates memory full condition on the device
+     * no longer exists.
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     *
+     * {@hide}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DEVICE_STORAGE_NOT_FULL = "android.intent.action.DEVICE_STORAGE_NOT_FULL";
+    /**
      * Broadcast Action:  Indicates low memory condition notification acknowledged by user
      * and package management should be started.
      * This is triggered by the user from the ACTION_DEVICE_STORAGE_LOW
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 22968b0..8a52e40 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.ContextMenu;
 import android.view.inputmethod.ExtractedText;
 import android.widget.EditText;
 
@@ -28,6 +29,7 @@
 public class ExtractEditText extends EditText {
     private InputMethodService mIME;
     private int mSettingExtractedText;
+    private boolean mContextMenuShouldBeHandledBySuper = false;
     
     public ExtractEditText(Context context) {
         super(context, null);
@@ -97,12 +99,19 @@
         return false;
     }
     
+    @Override
+    protected void onCreateContextMenu(ContextMenu menu) {
+        super.onCreateContextMenu(menu);
+        mContextMenuShouldBeHandledBySuper = true;
+    }
+
     @Override public boolean onTextContextMenuItem(int id) {
-        if (mIME != null) {
+        if (mIME != null && !mContextMenuShouldBeHandledBySuper) {
             if (mIME.onExtractTextContextMenuItem(id)) {
                 return true;
             }
         }
+        mContextMenuShouldBeHandledBySuper = false;
         return super.onTextContextMenuItem(id);
     }
     
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9fe6e01..32fb108 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -216,6 +216,11 @@
         public abstract Map<Integer, ? extends Sensor> getSensorStats();
 
         /**
+         * Returns a mapping containing active process data.
+         */
+        public abstract SparseArray<? extends Pid> getPidStats();
+        
+        /**
          * Returns a mapping containing process statistics.
          *
          * @return a Map from Strings to Uid.Proc objects.
@@ -286,6 +291,11 @@
             public abstract Timer getSensorTime();
         }
 
+        public class Pid {
+            public long mWakeSum;
+            public long mWakeStart;
+        }
+
         /**
          * The statistics associated with a particular process.
          */
@@ -521,6 +531,11 @@
     public abstract HistoryItem getHistory();
     
     /**
+     * Return the base time offset for the battery history.
+     */
+    public abstract long getHistoryBaseTime();
+    
+    /**
      * Returns the number of times the device has been started.
      */
     public abstract int getStartCount();
@@ -1571,8 +1586,10 @@
                         sb.append(prefix); sb.append("      CPU: ");
                                 formatTime(sb, userTime); sb.append("usr + ");
                                 formatTime(sb, systemTime); sb.append("krn\n");
-                        sb.append(prefix); sb.append("      "); sb.append(starts);
-                                sb.append(" proc starts");
+                        if (starts != 0) {
+                            sb.append(prefix); sb.append("      "); sb.append(starts);
+                                    sb.append(" proc starts");
+                        }
                         pw.println(sb.toString());
                         for (int e=0; e<numExcessive; e++) {
                             Uid.Proc.ExcessiveWake ew = ps.getExcessiveWake(e);
@@ -1673,6 +1690,7 @@
         HistoryItem rec = getHistory();
         if (rec != null) {
             pw.println("Battery History:");
+            long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
             int oldState = 0;
             int oldStatus = -1;
             int oldHealth = -1;
@@ -1681,7 +1699,7 @@
             int oldVolt = -1;
             while (rec != null) {
                 pw.print("  ");
-                pw.print(rec.time);
+                TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
                 pw.print(" ");
                 if (rec.cmd == HistoryItem.CMD_START) {
                     pw.println(" START");
@@ -1784,6 +1802,35 @@
                 oldState = rec.states;
                 rec = rec.next;
             }
+            pw.println("");
+        }
+        
+        SparseArray<? extends Uid> uidStats = getUidStats();
+        final int NU = uidStats.size();
+        boolean didPid = false;
+        long nowRealtime = SystemClock.elapsedRealtime();
+        StringBuilder sb = new StringBuilder(64);
+        for (int i=0; i<NU; i++) {
+            Uid uid = uidStats.valueAt(i);
+            SparseArray<? extends Uid.Pid> pids = uid.getPidStats();
+            if (pids != null) {
+                for (int j=0; j<pids.size(); j++) {
+                    Uid.Pid pid = pids.valueAt(j);
+                    if (!didPid) {
+                        pw.println("Per-PID Stats:");
+                        didPid = true;
+                    }
+                    long time = pid.mWakeSum + (pid.mWakeStart != 0
+                            ? (nowRealtime - pid.mWakeStart) : 0);
+                    pw.print("  PID "); pw.print(pids.keyAt(j));
+                            pw.print(" wake time: ");
+                            TimeUtils.formatDuration(time, pw);
+                            pw.println("");
+                }
+            }
+        }
+        if (didPid) {
+            pw.println("");
         }
         
         pw.println("Statistics since last charge:");
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 78a384b..7c80420 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2961,31 +2961,31 @@
         public static final String WTF_IS_FATAL = "wtf_is_fatal";
 
         /**
-         * Maximum age of entries kept by {@link android.os.IDropBox}.
+         * Maximum age of entries kept by {@link com.android.internal.os.IDropBoxManagerService}.
          * @hide
          */
         public static final String DROPBOX_AGE_SECONDS =
                 "dropbox_age_seconds";
         /**
-         * Maximum number of entry files which {@link android.os.IDropBox} will keep around.
+         * Maximum number of entry files which {@link com.android.internal.os.IDropBoxManagerService} will keep around.
          * @hide
          */
         public static final String DROPBOX_MAX_FILES =
                 "dropbox_max_files";
         /**
-         * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+         * Maximum amount of disk space used by {@link com.android.internal.os.IDropBoxManagerService} no matter what.
          * @hide
          */
         public static final String DROPBOX_QUOTA_KB =
                 "dropbox_quota_kb";
         /**
-         * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+         * Percent of free disk (excluding reserve) which {@link com.android.internal.os.IDropBoxManagerService} will use.
          * @hide
          */
         public static final String DROPBOX_QUOTA_PERCENT =
                 "dropbox_quota_percent";
         /**
-         * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+         * Percent of total disk which {@link com.android.internal.os.IDropBoxManagerService} will never dip into.
          * @hide
          */
         public static final String DROPBOX_RESERVE_PERCENT =
@@ -3045,6 +3045,15 @@
                 "sys_storage_threshold_percentage";
 
         /**
+         * Minimum bytes of free storage on the device before the data
+         * partition is considered full. By default, 1 MB is reserved
+         * to avoid system-wide SQLite disk full exceptions.
+         * @hide
+         */
+        public static final String SYS_STORAGE_FULL_THRESHOLD_BYTES =
+                "sys_storage_full_threshold_bytes";
+
+        /**
          * The interval in milliseconds after which Wi-Fi is considered idle.
          * When idle, it is possible for the device to be switched from Wi-Fi to
          * the mobile data network.
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index dde0889..4e2c3c3 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -168,6 +168,12 @@
     public static final int FORMAT_CAP_NOON = 0x00400;
     public static final int FORMAT_NO_MIDNIGHT = 0x00800;
     public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
+    /**
+     * @deprecated Use
+     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
+     * and pass in {@link Time#TIMEZONE_UTC Time.TIMEZONE_UTC} for the timeZone instead.
+     */
+    @Deprecated
     public static final int FORMAT_UTC = 0x02000;
     public static final int FORMAT_ABBREV_TIME = 0x04000;
     public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
@@ -946,12 +952,12 @@
      * {@link java.util.Formatter} instance and use the version of
      * {@link #formatDateRange(Context, long, long, int) formatDateRange}
      * that takes a {@link java.util.Formatter}.
-     * 
+     *
      * @param context the context is required only if the time is shown
      * @param startMillis the start time in UTC milliseconds
      * @param endMillis the end time in UTC milliseconds
      * @param flags a bit mask of options See
-     * {@link #formatDateRange(Context, long, long, int) formatDateRange}
+     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
      * @return a string containing the formatted date/time range.
      */
     public static String formatDateRange(Context context, long startMillis,
@@ -962,6 +968,29 @@
 
     /**
      * Formats a date or a time range according to the local conventions.
+     * <p>
+     * Note that this is a convenience method for formatting the date or
+     * time range in the local time zone. If you want to specify the time
+     * zone please use
+     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}.
+     *
+     * @param context the context is required only if the time is shown
+     * @param formatter the Formatter used for formatting the date range.
+     * Note: be sure to call setLength(0) on StringBuilder passed to
+     * the Formatter constructor unless you want the results to accumulate.
+     * @param startMillis the start time in UTC milliseconds
+     * @param endMillis the end time in UTC milliseconds
+     * @param flags a bit mask of options See
+     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
+     * @return a string containing the formatted date/time range.
+     */
+    public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
+            long endMillis, int flags) {
+        return formatDateRange(context, formatter, startMillis, endMillis, flags, null);
+    }
+
+    /**
+     * Formats a date or a time range according to the local conventions.
      * 
      * <p>
      * Example output strings (date formats in these examples are shown using
@@ -1076,8 +1105,9 @@
      * FORMAT_24HOUR takes precedence.
      * 
      * <p>
-     * If FORMAT_UTC is set, then the UTC timezone is used for the start
-     * and end milliseconds.
+     * If FORMAT_UTC is set, then the UTC time zone is used for the start
+     * and end milliseconds unless a time zone is specified. If a time zone
+     * is specified it will be used regardless of the FORMAT_UTC flag.
      * 
      * <p>
      * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the
@@ -1109,11 +1139,13 @@
      * @param startMillis the start time in UTC milliseconds
      * @param endMillis the end time in UTC milliseconds
      * @param flags a bit mask of options
-     *   
+     * @param timeZone the time zone to compute the string in. Use null for local
+     * or if the FORMAT_UTC flag is being used.
+     *  
      * @return the formatter with the formatted date/time range appended to the string buffer.
      */
     public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
-            long endMillis, int flags) {
+            long endMillis, int flags, String timeZone) {
         Resources res = Resources.getSystem();
         boolean showTime = (flags & FORMAT_SHOW_TIME) != 0;
         boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0;
@@ -1130,7 +1162,14 @@
         // computation below that'd otherwise be thrown out.
         boolean isInstant = (startMillis == endMillis);
 
-        Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+        Time startDate;
+        if (timeZone != null) {
+            startDate = new Time(timeZone);
+        } else if (useUTC) {
+            startDate = new Time(Time.TIMEZONE_UTC);
+        } else {
+            startDate = new Time();
+        }
         startDate.set(startMillis);
 
         Time endDate;
@@ -1139,7 +1178,13 @@
             endDate = startDate;
             dayDistance = 0;
         } else {
-            endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+            if (timeZone != null) {
+                endDate = new Time(timeZone);
+            } else if (useUTC) {
+                endDate = new Time(Time.TIMEZONE_UTC);
+            } else {
+                endDate = new Time();
+            }
             endDate.set(endMillis);
             int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
             int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 3fe14f9..0408673 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -34,7 +34,7 @@
      * An optional controller for the cursor.
      * Use {@link #setCursorController(CursorController)} to set this field.
      */
-    protected CursorController mCursorController;
+    private CursorController mCursorController;
 
     private boolean isCap(Spannable buffer) {
         return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
@@ -302,7 +302,17 @@
     /**
      * Defines the cursor controller.
      *
-     * When set, this object can be used to handle events, that can be translated in cursor updates.
+     * When set, this object can be used to handle touch events, that can be translated into cursor
+     * updates.
+     *
+     * {@link MotionEvent#ACTION_MOVE} events will call back the 
+     * {@link CursorController#updatePosition(int, int)} controller's method, passing the current
+     * finger coordinates (offset by {@link CursorController#getOffsetX()} and
+     * {@link CursorController#getOffsetY()}) as parameters. 
+     *
+     * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or
+     * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null.
+     *
      * @param cursorController A cursor controller implementation
      */
     public void setCursorController(CursorController cursorController) {
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/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index b01a71d..60ca384 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -132,20 +132,76 @@
         return ZoneInfoDB.getVersion();
     }
 
+    /** @hide Field length that can hold 999 days of time */
+    public static final int HUNDRED_DAY_FIELD_LEN = 19;
+    
     private static final int SECONDS_PER_MINUTE = 60;
     private static final int SECONDS_PER_HOUR = 60 * 60;
     private static final int SECONDS_PER_DAY = 24 * 60 * 60;
 
-    /** @hide Just for debugging; not internationalized. */
-    public static void formatDuration(long duration, StringBuilder builder) {
-        if (duration == 0) {
-            builder.append("0");
-            return;
+    private static final Object sFormatSync = new Object();
+    private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5];
+    
+    static private int accumField(int amt, int suffix, boolean always, int zeropad) {
+        if (amt > 99 || (always && zeropad >= 3)) {
+            return 3+suffix;
         }
+        if (amt > 9 || (always && zeropad >= 2)) {
+            return 2+suffix;
+        }
+        if (always || amt > 0) {
+            return 1+suffix;
+        }
+        return 0;
+    }
+    
+    static private int printField(char[] formatStr, int amt, char suffix, int pos,
+            boolean always, int zeropad) {
+        if (always || amt > 0) {
+            if ((always && zeropad >= 3) || amt > 99) {
+                int dig = amt/100;
+                formatStr[pos] = (char)(dig + '0');
+                pos++;
+                always = true;
+                amt -= (dig*100);
+            }
+            if ((always && zeropad >= 2) || amt > 9) {
+                int dig = amt/10;
+                formatStr[pos] = (char)(dig + '0');
+                pos++;
+                always = true;
+                amt -= (dig*10);
+            }
+            formatStr[pos] = (char)(amt + '0');
+            pos++;
+            formatStr[pos] = suffix;
+            pos++;
+        }
+        return pos;
+    }
+    
+    private static int formatDurationLocked(long duration, int fieldLen) {
+        if (sFormatStr.length < fieldLen) {
+            sFormatStr = new char[fieldLen];
+        }
+        
+        char[] formatStr = sFormatStr;
+        
+        if (duration == 0) {
+            int pos = 0;
+            fieldLen -= 1;
+            while (pos < fieldLen) {
+                formatStr[pos] = ' ';
+            }
+            formatStr[pos] = '0';
+            return pos+1;
+        }
+        
+        char prefix;
         if (duration > 0) {
-            builder.append("+");
+            prefix = '+';
         } else {
-            builder.append("-");
+            prefix = '-';
             duration = -duration;
         }
 
@@ -166,93 +222,62 @@
             seconds -= minutes * SECONDS_PER_MINUTE;
         }
 
-        boolean doall = false;
-        if (days > 0) {
-            builder.append(days);
-            builder.append('d');
-            doall = true;
+        int pos = 0;
+        
+        if (fieldLen != 0) {
+            int myLen = accumField(days, 1, false, 0);
+            myLen += accumField(hours, 1, myLen > 0, 2);
+            myLen += accumField(minutes, 1, myLen > 0, 2);
+            myLen += accumField(seconds, 1, myLen > 0, 2);
+            myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
+            while (myLen < fieldLen) {
+                formatStr[pos] = ' ';
+                pos++;
+                myLen++;
+            }
         }
-        if (doall || hours > 0) {
-            builder.append(hours);
-            builder.append('h');
-            doall = true;
+        
+        formatStr[pos] = prefix;
+        pos++;
+        
+        int start = pos;
+        boolean zeropad = fieldLen != 0;
+        pos = printField(formatStr, days, 'd', pos, false, 0);
+        pos = printField(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
+        pos = printField(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
+        pos = printField(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
+        pos = printField(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
+        formatStr[pos] = 's';
+        return pos + 1;
+    }
+    
+    /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long duration, StringBuilder builder) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, 0);
+            builder.append(sFormatStr, 0, len);
         }
-        if (doall || minutes > 0) {
-            builder.append(minutes);
-            builder.append('m');
-            doall = true;
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, fieldLen);
+            pw.print(new String(sFormatStr, 0, len));
         }
-        if (doall || seconds > 0) {
-            builder.append(seconds);
-            builder.append('s');
-            doall = true;
-        }
-        builder.append(millis);
-        builder.append("ms");
     }
 
     /** @hide Just for debugging; not internationalized. */
     public static void formatDuration(long duration, PrintWriter pw) {
-        if (duration == 0) {
-            pw.print("0");
-            return;
-        }
-        if (duration > 0) {
-            pw.print("+");
-        } else {
-            pw.print("-");
-            duration = -duration;
-        }
-
-        int millis = (int)(duration%1000);
-        int seconds = (int) Math.floor(duration / 1000);
-        int days = 0, hours = 0, minutes = 0;
-
-        if (seconds > SECONDS_PER_DAY) {
-            days = seconds / SECONDS_PER_DAY;
-            seconds -= days * SECONDS_PER_DAY;
-        }
-        if (seconds > SECONDS_PER_HOUR) {
-            hours = seconds / SECONDS_PER_HOUR;
-            seconds -= hours * SECONDS_PER_HOUR;
-        }
-        if (seconds > SECONDS_PER_MINUTE) {
-            minutes = seconds / SECONDS_PER_MINUTE;
-            seconds -= minutes * SECONDS_PER_MINUTE;
-        }
-
-        boolean doall = false;
-        if (days > 0) {
-            pw.print(days);
-            pw.print('d');
-            doall = true;
-        }
-        if (doall || hours > 0) {
-            pw.print(hours);
-            pw.print('h');
-            doall = true;
-        }
-        if (doall || minutes > 0) {
-            pw.print(minutes);
-            pw.print('m');
-            doall = true;
-        }
-        if (doall || seconds > 0) {
-            pw.print(seconds);
-            pw.print('s');
-            doall = true;
-        }
-        pw.print(millis);
-        pw.print("ms");
+        formatDuration(duration, pw, 0);
     }
-
-
+    
     /** @hide Just for debugging; not internationalized. */
     public static void formatDuration(long time, long now, PrintWriter pw) {
         if (time == 0) {
             pw.print("--");
             return;
         }
-        formatDuration(time-now, pw);
+        formatDuration(time-now, pw, 0);
     }
 }
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 74318ba..78b9b5d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -30,6 +30,7 @@
  */
 public final class MotionEvent extends InputEvent implements Parcelable {
     private static final long MS_PER_NS = 1000000;
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
     
     /**
      * Bit mask of the parts of the action code that are the action itself.
@@ -155,7 +156,17 @@
     @Deprecated
     public static final int ACTION_POINTER_ID_SHIFT = 8;
     
-    private static final boolean TRACK_RECYCLED_LOCATION = false;
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it.  This flag is set to true
+     * even if the event did not directly pass through the obscured area.
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
 
     /**
      * Flag indicating the motion event intersected the top edge of the screen.
@@ -251,6 +262,7 @@
     private float mYPrecision;
     private int mEdgeFlags;
     private int mMetaState;
+    private int mFlags;
     
     private int mNumPointers;
     private int mNumSamples;
@@ -338,20 +350,22 @@
      * @param deviceId The id for the device that this event came from.  An id of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
-     * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
      * MotionEvent.
      * @param source The source of this event.
+     * @param flags The motion event flags.
      */
     static public MotionEvent obtain(long downTime, long eventTime,
             int action, int pointers, int[] pointerIds, PointerCoords[] pointerCoords,
             int metaState, float xPrecision, float yPrecision, int deviceId,
-            int edgeFlags, int source) {
+            int edgeFlags, int source, int flags) {
         MotionEvent ev = obtain(pointers, 1);
         ev.mDeviceId = deviceId;
         ev.mSource = source;
         ev.mEdgeFlags = edgeFlags;
         ev.mDownTimeNano = downTime * MS_PER_NS;
         ev.mAction = action;
+        ev.mFlags = flags;
         ev.mMetaState = metaState;
         ev.mXOffset = 0;
         ev.mYOffset = 0;
@@ -401,7 +415,7 @@
      * @param deviceId The id for the device that this event came from.  An id of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
-     * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
      * MotionEvent.
      */
     static public MotionEvent obtain(long downTime, long eventTime, int action,
@@ -413,6 +427,7 @@
         ev.mEdgeFlags = edgeFlags;
         ev.mDownTimeNano = downTime * MS_PER_NS;
         ev.mAction = action;
+        ev.mFlags = 0;
         ev.mMetaState = metaState;
         ev.mXOffset = 0;
         ev.mYOffset = 0;
@@ -462,7 +477,7 @@
      * @param deviceId The id for the device that this event came from.  An id of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
-     * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
      * MotionEvent.
      * 
      * @deprecated Use {@link #obtain(long, long, int, float, float, float, float, int, float, float, int, int)}
@@ -509,6 +524,7 @@
         ev.mEdgeFlags = o.mEdgeFlags;
         ev.mDownTimeNano = o.mDownTimeNano;
         ev.mAction = o.mAction;
+        ev.mFlags = o.mFlags;
         ev.mMetaState = o.mMetaState;
         ev.mXOffset = o.mXOffset;
         ev.mYOffset = o.mYOffset;
@@ -540,6 +556,7 @@
         ev.mEdgeFlags = o.mEdgeFlags;
         ev.mDownTimeNano = o.mDownTimeNano;
         ev.mAction = o.mAction;
+        o.mFlags = o.mFlags;
         ev.mMetaState = o.mMetaState;
         ev.mXOffset = o.mXOffset;
         ev.mYOffset = o.mYOffset;
@@ -651,6 +668,15 @@
     }
 
     /**
+     * Gets the motion event flags.
+     *
+     * @see #FLAG_WINDOW_IS_OBSCURED
+     */
+    public final int getFlags() {
+        return mFlags;
+    }
+
+    /**
      * Returns the time (in ms) when the user originally pressed down to start
      * a stream of position events.
      */
@@ -1285,7 +1311,7 @@
 
 
     /**
-     * Sets the bitfield indicating which edges, if any, where touched by this
+     * Sets the bitfield indicating which edges, if any, were touched by this
      * MotionEvent.
      *
      * @see #getEdgeFlags()
@@ -1480,6 +1506,7 @@
         ev.mYPrecision = in.readFloat();
         ev.mEdgeFlags = in.readInt();
         ev.mMetaState = in.readInt();
+        ev.mFlags = in.readInt();
         
         final int[] pointerIdentifiers = ev.mPointerIdentifiers;
         for (int i = 0; i < NP; i++) {
@@ -1521,6 +1548,7 @@
         out.writeFloat(mYPrecision);
         out.writeInt(mEdgeFlags);
         out.writeInt(mMetaState);
+        out.writeInt(mFlags);
         
         final int[] pointerIdentifiers = mPointerIdentifiers;
         for (int i = 0; i < NP; i++) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7332c16..fe003a4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -542,6 +542,28 @@
  * take care of redrawing the appropriate views until the animation completes.
  * </p>
  *
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ * <p>
+ * Sometimes it is essential that an application be able to verify that an action
+ * is being performed with the full knowledge and consent of the user, such as
+ * granting a permission request, making a purchase or clicking on an advertisement.
+ * Unfortunately, a malicious application could try to spoof the user into
+ * performing these actions, unaware, by concealing the intended purpose of the view.
+ * As a remedy, the framework offers a touch filtering mechanism that can be used to
+ * improve the security of views that provide access to sensitive functionality.
+ * </p><p>
+ * To enable touch filtering, call {@link #setFilterTouchesWhenObscured} or set the
+ * andoird:filterTouchesWhenObscured attribute to true.  When enabled, the framework
+ * will discard touches that are received whenever the view's window is obscured by
+ * another visible window.  As a result, the view will not receive touches whenever a
+ * toast, dialog or other window appears above the view's window.
+ * </p><p>
+ * For more fine-grained control over security, consider overriding the
+ * {@link #onFilterTouchEventForSecurity} method to implement your own security policy.
+ * See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
+ * </p>
+ *
  * @attr ref android.R.styleable#View_background
  * @attr ref android.R.styleable#View_clickable
  * @attr ref android.R.styleable#View_contentDescription
@@ -550,6 +572,7 @@
  * @attr ref android.R.styleable#View_id
  * @attr ref android.R.styleable#View_fadingEdge
  * @attr ref android.R.styleable#View_fadingEdgeLength
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
  * @attr ref android.R.styleable#View_fitsSystemWindows
  * @attr ref android.R.styleable#View_isScrollContainer
  * @attr ref android.R.styleable#View_focusable
@@ -711,7 +734,14 @@
      */
     static final int SCROLLBARS_MASK = 0x00000300;
 
-    // note 0x00000400 and 0x00000800 are now available for next flags...
+    /**
+     * Indicates that the view should filter touches when its window is obscured.
+     * Refer to the class comments for more information about this security feature.
+     * {@hide}
+     */
+    static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
+
+    // note flag value 0x00000800 is now available for next flags...
 
     /**
      * <p>This view doesn't show fading edges.</p>
@@ -2052,6 +2082,12 @@
                         viewFlagMasks |= KEEP_SCREEN_ON;
                     }
                     break;
+                case R.styleable.View_filterTouchesWhenObscured:
+                    if (a.getBoolean(attr, false)) {
+                        viewFlagValues |= FILTER_TOUCHES_WHEN_OBSCURED;
+                        viewFlagMasks |= FILTER_TOUCHES_WHEN_OBSCURED;
+                    }
+                    break;
                 case R.styleable.View_nextFocusLeft:
                     mNextFocusLeftId = a.getResourceId(attr, View.NO_ID);
                     break;
@@ -3389,6 +3425,35 @@
         setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK);
     }
 
+    /**
+     * Gets whether the framework should discard touches when the view's
+     * window is obscured by another visible window.
+     * Refer to the {@link View} security documentation for more details.
+     *
+     * @return True if touch filtering is enabled.
+     *
+     * @see #setFilterTouchesWhenObscured(boolean)
+     * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+     */
+    @ViewDebug.ExportedProperty
+    public boolean getFilterTouchesWhenObscured() {
+        return (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0;
+    }
+
+    /**
+     * Sets whether the framework should discard touches when the view's
+     * window is obscured by another visible window.
+     * Refer to the {@link View} security documentation for more details.
+     *
+     * @param enabled True if touch filtering should be enabled.
+     *
+     * @see #getFilterTouchesWhenObscured
+     * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+     */
+    public void setFilterTouchesWhenObscured(boolean enabled) {
+        setFlags(enabled ? 0 : FILTER_TOUCHES_WHEN_OBSCURED,
+                FILTER_TOUCHES_WHEN_OBSCURED);
+    }
 
     /**
      * Returns whether this View is able to take focus.
@@ -3808,6 +3873,10 @@
      * @return True if the event was handled by the view, false otherwise.
      */
     public boolean dispatchTouchEvent(MotionEvent event) {
+        if (!onFilterTouchEventForSecurity(event)) {
+            return false;
+        }
+
         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                 mOnTouchListener.onTouch(this, event)) {
             return true;
@@ -3816,6 +3885,23 @@
     }
 
     /**
+     * Filter the touch event to apply security policies.
+     *
+     * @param event The motion event to be filtered.
+     * @return True if the event should be dispatched, false if the event should be dropped.
+     * 
+     * @see #getFilterTouchesWhenObscured
+     */
+    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
+                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
+            // Window is obscured, drop this touch.
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Pass a trackball motion event down to the focused view.
      *
      * @param event The motion event to be dispatched.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7159929..28bed3a 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -822,6 +822,10 @@
      */
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (!onFilterTouchEventForSecurity(ev)) {
+            return false;
+        }
+
         final int action = ev.getAction();
         final float xf = ev.getX();
         final float yf = ev.getY();
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 659f9cd..76701a9 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -74,6 +74,8 @@
     public final static int FLAG_MENU = 0x00000040;
     public final static int FLAG_LAUNCHER = 0x00000080;
 
+    public final static int FLAG_INJECTED = 0x01000000;
+
     public final static int FLAG_WOKE_HERE = 0x10000000;
     public final static int FLAG_BRIGHT_HERE = 0x20000000;
 
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 07c3e4b..4bbb540 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -236,6 +236,7 @@
                         trigger = true;
                         createUri = Uri.fromParts("tel", (String)cookie, null);
 
+                        //$FALL-THROUGH$
                     case TOKEN_PHONE_LOOKUP: {
                         if (cursor != null && cursor.moveToFirst()) {
                             long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX);
@@ -249,12 +250,14 @@
                         trigger = true;
                         createUri = Uri.fromParts("mailto", (String)cookie, null);
 
+                        //$FALL-THROUGH$
                     case TOKEN_EMAIL_LOOKUP: {
                         if (cursor != null && cursor.moveToFirst()) {
                             long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX);
                             String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX);
                             lookupUri = Contacts.getLookupUri(contactId, lookupKey);
                         }
+                        break;
                     }
 
                     case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1e8023c..3428206 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -35,6 +35,7 @@
 import android.graphics.RectF;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.inputmethodservice.ExtractEditText;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -3674,18 +3675,21 @@
 
         boolean changed = false;
 
+        SelectionModifierCursorController selectionController = null;
+        if (mSelectionModifierCursorController != null) {
+            selectionController = (SelectionModifierCursorController)
+                mSelectionModifierCursorController;
+        }
+
+
         if (mMovement != null) {
             /* This code also provides auto-scrolling when a cursor is moved using a
              * CursorController (insertion point or selection limits).
              * For selection, ensure start or end is visible depending on controller's state.
              */
             int curs = getSelectionEnd();
-            if (mSelectionModifierCursorController != null) {
-                SelectionModifierCursorController selectionController =
-                    (SelectionModifierCursorController) mSelectionModifierCursorController;
-                if (selectionController.isSelectionStartDragged()) {
-                    curs = getSelectionStart();
-                }
+            if (selectionController != null && selectionController.isSelectionStartDragged()) {
+                curs = getSelectionStart();
             }
 
             /*
@@ -3705,10 +3709,16 @@
             changed = bringTextIntoView();
         }
 
-        if (mShouldStartTextSelectionMode) {
+        // This has to be checked here since:
+        // - onFocusChanged cannot start it when focus is given to a view with selected text (after
+        //   a screen rotation) since layout is not yet initialized at that point.
+        // - ExtractEditText does not call onFocus when it is displayed. Fixing this issue would
+        //   allow to test for hasSelection in onFocusChanged, which would trigger a
+        //   startTextSelectionMode here. TODO
+        if (selectionController != null && hasSelection()) {
             startTextSelectionMode();
-            mShouldStartTextSelectionMode = false;
         }
+
         mPreDrawState = PREDRAW_DONE;
         return !changed;
     }
@@ -5807,7 +5817,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;
     }
 
     /**
@@ -6471,19 +6484,15 @@
         mShowCursor = SystemClock.uptimeMillis();
 
         ensureEndedBatchEdit();
-        
+
         if (focused) {
             int selStart = getSelectionStart();
             int selEnd = getSelectionEnd();
 
             if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
-                boolean selMoved = mSelectionMoved;
-
-                if (mSelectionModifierCursorController != null) {
-                    final int touchOffset = 
-                        ((SelectionModifierCursorController) mSelectionModifierCursorController).
-                        getMinTouchOffset();
-                    Selection.setSelection((Spannable) mText, touchOffset);
+                // Has to be done before onTakeFocus, which can be overloaded.
+                if (mLastTouchOffset >= 0) {
+                    Selection.setSelection((Spannable) mText, mLastTouchOffset);
                 }
 
                 if (mMovement != null) {
@@ -6494,7 +6503,12 @@
                     Selection.setSelection((Spannable) mText, 0, mText.length());
                 }
 
-                if (selMoved && selStart >= 0 && selEnd >= 0) {
+                // The DecorView does not have focus when the 'Done' ExtractEditText button is
+                // pressed. Since it is the ViewRoot's mView, it requests focus before
+                // 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) {
                     /*
                      * Someone intentionally set the selection, so let them
                      * do whatever it is that they wanted to do instead of
@@ -6504,7 +6518,6 @@
                      * just setting the selection in theirs and we still
                      * need to go through that path.
                      */
-
                     Selection.setSelection((Spannable) mText, selStart, selEnd);
                 }
                 mTouchFocusSelected = true;
@@ -6523,13 +6536,6 @@
             if (mError != null) {
                 showError();
             }
-
-            // We cannot start the selection mode immediately. The layout may be null here and is
-            // needed by the cursor controller. Layout creation is deferred up to drawing. The
-            // selection action mode will be started in onPreDraw().
-            if (selStart != selEnd) {
-                mShouldStartTextSelectionMode = true;
-            }
         } else {
             if (mError != null) {
                 hideError();
@@ -6538,14 +6544,19 @@
             onEndBatchEdit();
 
             hideInsertionPointCursorController();
-            terminateTextSelectionMode();
+            if (this instanceof ExtractEditText) {
+                // terminateTextSelectionMode would remove selection, which we want to keep when
+                // ExtractEditText goes out of focus.
+                mIsInTextSelectionMode = false;
+            } else {
+                terminateTextSelectionMode();
+            }
         }
 
         startStopMarquee(focused);
 
         if (mTransformation != null) {
-            mTransformation.onFocusChanged(this, mText, focused, direction,
-                                           previouslyFocusedRect);
+            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
         }
 
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
@@ -6604,60 +6615,57 @@
         }
     }
 
-    class CommitSelectionReceiver extends ResultReceiver {
-        private final int mPrevStart, mPrevEnd;
-        private final int mNewStart, mNewEnd;
-        
-        public CommitSelectionReceiver(int mPrevStart, int mPrevEnd, int mNewStart, int mNewEnd) {
-            super(getHandler());
-            this.mPrevStart = mPrevStart;
-            this.mPrevEnd = mPrevEnd;
-            this.mNewStart = mNewStart;
-            this.mNewEnd = mNewEnd;
-        }
-        
-        @Override
-        protected void onReceiveResult(int resultCode, Bundle resultData) {
-            int start = mNewStart;
-            int end = mNewEnd;
+    private void onTapUpEvent(int prevStart, int prevEnd) {
+        final int start = getSelectionStart();
+        final int end = getSelectionEnd();
 
-            // Move the cursor to the new position, unless this tap was actually
-            // use to show the IMM. Leave cursor unchanged in that case.
-            if (resultCode == InputMethodManager.RESULT_SHOWN) {
-                start = mPrevStart;
-                end = mPrevEnd;
+        if (start == end) {
+            if (start >= prevStart && start < prevEnd) {
+                // Tapping inside the selection displays the cut/copy/paste context menu.
+                showContextMenu();
+                return;
             } else {
-                if ((mPrevStart != mPrevEnd) && (start == end)) {
-                    if ((start >= mPrevStart) && (start < mPrevEnd)) {
-                        // Tapping inside the selection does nothing
-                        Selection.setSelection((Spannable) mText, mPrevStart, mPrevEnd);
-                        showContextMenu();
-                        return;
-                    } else {
-                        // Tapping outside stops selection mode, if any
-                        stopTextSelectionMode();
-                    }
-                }
+                // Tapping outside stops selection mode, if any
+                stopTextSelectionMode();
 
                 if (mInsertionPointCursorController != null) {
                     mInsertionPointCursorController.show();
                 }
             }
+        }
+    }
 
-            final int len = mText.length();
-            if (start > len) {
-                start = len;
+    class CommitSelectionReceiver extends ResultReceiver {
+        private final int mPrevStart, mPrevEnd;
+        
+        public CommitSelectionReceiver(int prevStart, int prevEnd) {
+            super(getHandler());
+            mPrevStart = prevStart;
+            mPrevEnd = prevEnd;
+        }
+        
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            // If this tap was actually used to show the IMM, leave cursor or selection unchanged
+            // by restoring its previous position.
+            if (resultCode == InputMethodManager.RESULT_SHOWN) {
+                final int len = mText.length();
+                int start = Math.min(len, mPrevStart);
+                int end = Math.min(len, mPrevEnd);
+                Selection.setSelection((Spannable)mText, start, end);
+
+                if (hasSelection()) {
+                    startTextSelectionMode();
+                } else if (mInsertionPointCursorController != null) {
+                    mInsertionPointCursorController.show();
+                }
             }
-            if (end > len) {
-                end = len;
-            }
-            Selection.setSelection((Spannable)mText, start, end);
         }
     }
     
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        final int action = event.getAction();
+        final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             // Reset this state; it will be re-set if super.onTouchEvent
             // causes focus to move to the view.
@@ -6678,10 +6686,7 @@
         }
 
         if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
-            
-            int oldSelStart = getSelectionStart();
-            int oldSelEnd = getSelectionEnd();
-            
+
             if (mInsertionPointCursorController != null) {
                 mInsertionPointCursorController.onTouchEvent(event);
             }
@@ -6690,6 +6695,10 @@
             }
 
             boolean handled = false;
+
+            // Save previous selection, in case this event is used to show the IME.
+            int oldSelStart = getSelectionStart();
+            int oldSelEnd = getSelectionEnd();
             
             if (mMovement != null) {
                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
@@ -6699,18 +6708,18 @@
                 if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
                     InputMethodManager imm = (InputMethodManager)
                           getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-                    
-                    final int newSelStart = getSelectionStart();
-                    final int newSelEnd = getSelectionEnd();
-                    
+
                     CommitSelectionReceiver csr = null;
-                    if (newSelStart != oldSelStart || newSelEnd != oldSelEnd ||
+                    if (getSelectionStart() != oldSelStart || getSelectionEnd() != oldSelEnd ||
                             didTouchFocusSelect()) {
-                        csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd,
-                                newSelStart, newSelEnd);
+                        csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd);
                     }
-                    
+
                     handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
+
+                    // Cannot be done by CommitSelectionReceiver, which might not always be called,
+                    // for instance when dealing with an ExtractEditText.
+                    onTapUpEvent(oldSelStart, oldSelEnd);
                 }
             }
 
@@ -6999,7 +7008,7 @@
             return false;
         }
 
-        if (mText.length() > 0 && getSelectionStart() >= 0) {
+        if (mText.length() > 0 && hasSelection()) {
             if (mText instanceof Editable && mInput != null) {
                 return true;
             }
@@ -7013,7 +7022,7 @@
             return false;
         }
 
-        if (mText.length() > 0 && getSelectionStart() >= 0) {
+        if (mText.length() > 0 && hasSelection()) {
             return true;
         }
 
@@ -7134,6 +7143,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);
@@ -7152,14 +7204,11 @@
     }
     
     private String getWordForDictionary() {
-        if (mSelectionModifierCursorController == null) {
+        if (mLastTouchOffset < 0) {
             return null;
         }
 
-        int offset = ((SelectionModifierCursorController) mSelectionModifierCursorController).
-                     getMinTouchOffset();
-
-        long wordLimits = getWordLimitsAt(offset);
+        long wordLimits = getWordLimitsAt(mLastTouchOffset);
         if (wordLimits >= 0) {
             int start = (int) (wordLimits >>> 32);
             int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
@@ -7167,7 +7216,6 @@
         } else {
             return null;
         }
-        
     }
     
     @Override
@@ -7218,21 +7266,22 @@
                 menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
                      setOnMenuItemClickListener(handler).
                      setAlphabeticShortcut('x');
+                added = true;
             }
 
             if (canCopy()) {
                 menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
                      setOnMenuItemClickListener(handler).
                      setAlphabeticShortcut('c');
+                added = true;
             }
 
             if (canPaste()) {
                 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
                      setOnMenuItemClickListener(handler).
                      setAlphabeticShortcut('v');
+                added = true;
             }
-
-            added = true;
         } else {
             /*
             if (!isFocused()) {
@@ -7311,11 +7360,10 @@
 
     private boolean textIsOnlySpaces() {
         final int length = mTransformed.length();
-        for (int i=0; i<length; i++) {
-            final char c = mTransformed.charAt(i);
-            final int type = Character.getType(c);
-            if (type != Character.SPACE_SEPARATOR)
+        for (int i = 0; i < length; i++) {
+            if (!Character.isSpaceChar(mTransformed.charAt(i))) {
                 return false;
+            }
         }
         return true;
     }
@@ -7348,7 +7396,7 @@
     /**
      * Called when a context menu option for the text view is selected.  Currently
      * this will be one of: {@link android.R.id#selectAll},
-     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+     * {@link android.R.id#startSelectingText},
      * {@link android.R.id#cut}, {@link android.R.id#copy},
      * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
      * or {@link android.R.id#switchInputMethod}.
@@ -7392,7 +7440,37 @@
             case ID_PASTE:
                 CharSequence paste = clip.getText();
 
-                if (paste != null) {
+                if (paste != null && paste.length() > 0) {
+                    // Paste adds/removes spaces before or after insertion as needed.
+
+                    if (Character.isSpaceChar(paste.charAt(0))) {
+                        if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+                            // Two spaces at beginning of paste: remove one
+                            ((Editable) mText).replace(min - 1, min, "");
+                            min = min - 1;
+                            max = max - 1;
+                        }
+                    } else {
+                        if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+                            // No space at beginning of paste: add one
+                            ((Editable) mText).replace(min, min, " ");
+                            min = min + 1;
+                            max = max + 1;
+                        }
+                    }
+
+                    if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
+                        if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
+                            // Two spaces at end of paste: remove one
+                            ((Editable) mText).replace(max, max + 1, "");
+                        }
+                    } else {
+                        if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
+                            // No space at end of paste: add one
+                            ((Editable) mText).replace(max, max, " ");
+                        }
+                    }
+
                     Selection.setSelection((Spannable) mText, max);
                     ((Editable) mText).replace(min, max, paste);
                     stopTextSelectionMode();
@@ -7439,18 +7517,20 @@
     }
 
     private void startTextSelectionMode() {
-        if (mSelectionModifierCursorController == null) {
-            Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
-            return;
-        }
+        if (!mIsInTextSelectionMode) {
+            if (mSelectionModifierCursorController == null) {
+                Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
+                return;
+            }
 
-        if (!requestFocus()) {
-            return;
-        }
+            if (!requestFocus()) {
+                return;
+            }
 
-        selectCurrentWord();
-        mSelectionModifierCursorController.show();
-        mIsInTextSelectionMode = true;
+            selectCurrentWord();
+            mSelectionModifierCursorController.show();
+            mIsInTextSelectionMode = true;
+        }
     }
     
     /**
@@ -7555,8 +7635,9 @@
             mHotSpotVerticalPosition = lineTop;
 
             final Rect bounds = sCursorControllerTempRect;
-            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0);
-            bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2;
+            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0)
+                + mScrollX;
+            bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2 + mScrollY;
 
             mTopExtension = bottom ? 0 : drawableHeight / 2;
             mBottomExtension = drawableHeight;
@@ -7587,6 +7668,7 @@
                            (int) (y - mBottomExtension),
                            (int) (x + drawableWidth / 2.0),
                            (int) (y + mTopExtension));
+            fingerRect.offset(mScrollX, mScrollY);
             return Rect.intersects(mDrawable.getBounds(), fingerRect);
         }
 
@@ -7865,7 +7947,8 @@
                 return;
             }
 
-            boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) == mLayout.getLineForOffset(selectionEnd); 
+            boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) ==
+                mLayout.getLineForOffset(selectionEnd);
             mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
             mEndHandle.positionAtCursor(selectionEnd, true);
 
@@ -7881,7 +7964,7 @@
                         final int y = (int) event.getY();
 
                         // Remember finger down position, to be able to start selection from there
-                        mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
+                        mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y);
 
                         if (mIsVisible) {
                             if (mMovement instanceof ArrowKeyMovementMethod) {
@@ -7897,7 +7980,8 @@
                                     // In case both controllers are under finger (very small
                                     // selection region), arbitrarily pick end controller.
                                     mStartIsDragged = !isOnEnd;
-                                    final Handle draggedHandle = mStartIsDragged ? mStartHandle : mEndHandle;
+                                    final Handle draggedHandle =
+                                        mStartIsDragged ? mStartHandle : mEndHandle;
                                     final Rect bounds = draggedHandle.mDrawable.getBounds();
                                     mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
                                     mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;
@@ -8071,8 +8155,8 @@
     // Cursor Controllers. Null when disabled.
     private CursorController        mInsertionPointCursorController;
     private CursorController        mSelectionModifierCursorController;
-    private boolean                 mShouldStartTextSelectionMode = false;
     private boolean                 mIsInTextSelectionMode = false;
+    private int                     mLastTouchOffset = -1;
     // Created once and shared by different CursorController helper methods.
     // Only one cursor controller is active at any time which prevent race conditions.
     private static Rect             sCursorControllerTempRect = new Rect();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f4447ab..b73b78b 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -22,6 +22,8 @@
 import android.net.TrafficStats;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFormatException;
 import android.os.Parcelable;
@@ -79,6 +81,38 @@
 
     private final JournaledFile mFile;
 
+    static final int MSG_UPDATE_WAKELOCKS = 1;
+    static final int MSG_REPORT_POWER_CHANGE = 2;
+    static final long DELAY_UPDATE_WAKELOCKS = 15*1000;
+
+    public interface BatteryCallback {
+        public void batteryNeedsCpuUpdate();
+        public void batteryPowerChanged(boolean onBattery);
+    }
+
+    final class MyHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            BatteryCallback cb = mCallback;
+            switch (msg.what) {
+                case MSG_UPDATE_WAKELOCKS:
+                    if (cb != null) {
+                        cb.batteryNeedsCpuUpdate();
+                    }
+                    break;
+                case MSG_REPORT_POWER_CHANGE:
+                    if (cb != null) {
+                        cb.batteryPowerChanged(msg.arg1 != 0);
+                    }
+                    break;
+            }
+        }
+    }
+
+    private final MyHandler mHandler;
+
+    private BatteryCallback mCallback;
+
     /**
      * The statistics we have collected organized by uids.
      */
@@ -95,6 +129,9 @@
     final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers
             = new SparseArray<ArrayList<StopwatchTimer>>();
 
+    // Last partial timers we use for distributing CPU usage.
+    final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>();
+
     // These are the objects that will want to do something when the device
     // is unplugged from power.
     final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
@@ -240,6 +277,7 @@
     // For debugging
     public BatteryStatsImpl() {
         mFile = null;
+        mHandler = null;
     }
 
     public static interface Unpluggable {
@@ -739,7 +777,9 @@
      * State for keeping track of timing information.
      */
     public static final class StopwatchTimer extends Timer {
+        final Uid mUid;
         final ArrayList<StopwatchTimer> mTimerPool;
+
         int mNesting;
 
         /**
@@ -757,16 +797,24 @@
 
         long mTimeout;
 
-        StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool,
+        /**
+         * For partial wake locks, keep track of whether we are in the list
+         * to consume CPU cycles.
+         */
+        boolean mInList;
+
+        StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
                 ArrayList<Unpluggable> unpluggables, Parcel in) {
             super(type, unpluggables, in);
+            mUid = uid;
             mTimerPool = timerPool;
             mUpdateTime = in.readLong();
         }
 
-        StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool,
+        StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
                 ArrayList<Unpluggable> unpluggables) {
             super(type, unpluggables);
+            mUid = uid;
             mTimerPool = timerPool;
         }
         
@@ -1252,6 +1300,10 @@
             mWakeLockNesting++;
         }
         if (uid >= 0) {
+            if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+                Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+                mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+            }
             getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type);
         }
     }
@@ -1267,10 +1319,112 @@
             }
         }
         if (uid >= 0) {
+            if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+                Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+                mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+            }
             getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type);
         }
     }
 
+    public int startAddingCpuLocked() {
+        mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
+
+        if (mScreenOn) {
+            return 0;
+        }
+
+        final int N = mPartialTimers.size();
+        if (N == 0) {
+            mLastPartialTimers.clear();
+            return 0;
+        }
+
+        // How many timers should consume CPU?  Only want to include ones
+        // that have already been in the list.
+        for (int i=0; i<N; i++) {
+            StopwatchTimer st = mPartialTimers.get(i);
+            if (st.mInList) {
+                Uid uid = st.mUid;
+                // We don't include the system UID, because it so often
+                // holds wake locks at one request or another of an app.
+                if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+                    return 50;
+                }
+            }
+        }
+
+        return 0;
+    }
+
+    public void finishAddingCpuLocked(int perc, int utime, int stime, long[] cpuSpeedTimes) {
+        final int N = mPartialTimers.size();
+        if (perc != 0) {
+            int num = 0;
+            for (int i=0; i<N; i++) {
+                StopwatchTimer st = mPartialTimers.get(i);
+                if (st.mInList) {
+                    Uid uid = st.mUid;
+                    // We don't include the system UID, because it so often
+                    // holds wake locks at one request or another of an app.
+                    if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+                        num++;
+                    }
+                }
+            }
+            if (num != 0) {
+                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) {
+                            Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*");
+                            proc.addCpuTimeLocked(myUTime, mySTime);
+                            proc.addSpeedStepTimes(cpuSpeedTimes);
+                        }
+                    }
+                }
+            }
+
+            // Just in case, collect any lost CPU time.
+            if (utime != 0 || stime != 0) {
+                Uid uid = getUidStatsLocked(Process.SYSTEM_UID);
+                if (uid != null) {
+                    Uid.Proc proc = uid.getProcessStatsLocked("*lost*");
+                    proc.addCpuTimeLocked(utime, stime);
+                    proc.addSpeedStepTimes(cpuSpeedTimes);
+                }
+            }
+        }
+
+        final int NL = mLastPartialTimers.size();
+        boolean diff = N != NL;
+        for (int i=0; i<NL && !diff; i++) {
+            diff |= mPartialTimers.get(i) != mLastPartialTimers.get(i);
+        }
+        if (!diff) {
+            for (int i=0; i<NL; i++) {
+                mPartialTimers.get(i).mInList = true;
+            }
+            return;
+        }
+
+        for (int i=0; i<NL; i++) {
+            mLastPartialTimers.get(i).mInList = false;
+        }
+        mLastPartialTimers.clear();
+        for (int i=0; i<N; i++) {
+            StopwatchTimer st = mPartialTimers.get(i);
+            st.mInList = true;
+            mLastPartialTimers.add(st);
+        }
+    }
+
     public void noteProcessDiedLocked(int uid, int pid) {
         Uid u = mUidStats.get(uid);
         if (u != null) {
@@ -1922,13 +2076,18 @@
 
         public Uid(int uid) {
             mUid = uid;
-            mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables);
-            mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK, null, mUnpluggables);
-            mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables);
-            mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
+            mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
                     null, mUnpluggables);
-            mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON, null, mUnpluggables);
-            mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON, null, mUnpluggables);
+            mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
+                    null, mUnpluggables);
+            mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
+                    null, mUnpluggables);
+            mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
+                    null, mUnpluggables);
+            mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+                    null, mUnpluggables);
+            mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
+                    null, mUnpluggables);
         }
 
         @Override
@@ -1996,7 +2155,7 @@
             if (!mWifiTurnedOn) {
                 mWifiTurnedOn = true;
                 if (mWifiTurnedOnTimer == null) {
-                    mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON,
+                    mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
                             null, mUnpluggables);
                 }
                 mWifiTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2016,7 +2175,7 @@
             if (!mFullWifiLockOut) {
                 mFullWifiLockOut = true;
                 if (mFullWifiLockTimer == null) {
-                    mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK,
+                    mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
                             null, mUnpluggables);
                 }
                 mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2036,7 +2195,7 @@
             if (!mScanWifiLockOut) {
                 mScanWifiLockOut = true;
                 if (mScanWifiLockTimer == null) {
-                    mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK,
+                    mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
                             null, mUnpluggables);
                 }
                 mScanWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2056,7 +2215,7 @@
             if (!mWifiMulticastEnabled) {
                 mWifiMulticastEnabled = true;
                 if (mWifiMulticastTimer == null) {
-                    mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
+                    mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
                             null, mUnpluggables);
                 }
                 mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2076,7 +2235,7 @@
             if (!mAudioTurnedOn) {
                 mAudioTurnedOn = true;
                 if (mAudioTurnedOnTimer == null) {
-                    mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON,
+                    mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
                             null, mUnpluggables);
                 }
                 mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2096,7 +2255,7 @@
             if (!mVideoTurnedOn) {
                 mVideoTurnedOn = true;
                 if (mVideoTurnedOnTimer == null) {
-                    mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON,
+                    mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
                             null, mUnpluggables);
                 }
                 mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2456,42 +2615,42 @@
             mTcpBytesSentAtLastUnplug = in.readLong();
             mWifiTurnedOn = false;
             if (in.readInt() != 0) {
-                mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON,
+                mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
                         null, mUnpluggables, in);
             } else {
                 mWifiTurnedOnTimer = null;
             }
             mFullWifiLockOut = false;
             if (in.readInt() != 0) {
-                mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK,
+                mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
                         null, mUnpluggables, in);
             } else {
                 mFullWifiLockTimer = null;
             }
             mScanWifiLockOut = false;
             if (in.readInt() != 0) {
-                mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK,
+                mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
                         null, mUnpluggables, in);
             } else {
                 mScanWifiLockTimer = null;
             }
             mWifiMulticastEnabled = false;
             if (in.readInt() != 0) {
-                mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
+                mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
                         null, mUnpluggables, in);
             } else {
                 mWifiMulticastTimer = null;
             }
             mAudioTurnedOn = false;
             if (in.readInt() != 0) {
-                mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON,
+                mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
                         null, mUnpluggables, in);
             } else {
                 mAudioTurnedOnTimer = null;
             }
             mVideoTurnedOn = false;
             if (in.readInt() != 0) {
-                mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON,
+                mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
                         null, mUnpluggables, in);
             } else {
                 mVideoTurnedOnTimer = null;
@@ -2538,7 +2697,7 @@
                     return null;
                 }
 
-                return new StopwatchTimer(type, pool, unpluggables, in);
+                return new StopwatchTimer(Uid.this, type, pool, unpluggables, in);
             }
 
             boolean reset() {
@@ -2614,7 +2773,7 @@
                     pool = new ArrayList<StopwatchTimer>();
                     mSensorTimers.put(mHandle, pool);
                 }
-                return new StopwatchTimer(0, pool, unpluggables, in);
+                return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in);
             }
 
             boolean reset() {
@@ -3343,11 +3502,6 @@
             }
         }
 
-        public class Pid {
-            long mWakeSum;
-            long mWakeStart;
-        }
-
         /**
          * Retrieve the statistics object for a particular process, creating
          * if needed.
@@ -3362,6 +3516,10 @@
             return ps;
         }
 
+        public SparseArray<? extends Pid> getPidStats() {
+            return mPids;
+        }
+        
         public Pid getPidStatsLocked(int pid) {
             Pid p = mPids.get(pid);
             if (p == null) {
@@ -3417,21 +3575,24 @@
                 case WAKE_TYPE_PARTIAL:
                     t = wl.mTimerPartial;
                     if (t == null) {
-                        t = new StopwatchTimer(WAKE_TYPE_PARTIAL, mPartialTimers, mUnpluggables);
+                        t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL,
+                                mPartialTimers, mUnpluggables);
                         wl.mTimerPartial = t;
                     }
                     return t;
                 case WAKE_TYPE_FULL:
                     t = wl.mTimerFull;
                     if (t == null) {
-                        t = new StopwatchTimer(WAKE_TYPE_FULL, mFullTimers, mUnpluggables);
+                        t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL,
+                                mFullTimers, mUnpluggables);
                         wl.mTimerFull = t;
                     }
                     return t;
                 case WAKE_TYPE_WINDOW:
                     t = wl.mTimerWindow;
                     if (t == null) {
-                        t = new StopwatchTimer(WAKE_TYPE_WINDOW, mWindowTimers, mUnpluggables);
+                        t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW,
+                                mWindowTimers, mUnpluggables);
                         wl.mTimerWindow = t;
                     }
                     return t;
@@ -3458,7 +3619,7 @@
                 timers = new ArrayList<StopwatchTimer>();
                 mSensorTimers.put(sensor, timers);
             }
-            t = new StopwatchTimer(BatteryStats.SENSOR, timers, mUnpluggables);
+            t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables);
             se.mTimer = t;
             return t;
         }
@@ -3531,25 +3692,26 @@
 
     public BatteryStatsImpl(String filename) {
         mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
+        mHandler = new MyHandler();
         mStartCount++;
-        mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables);
+        mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
-            mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables);
+            mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables);
         }
         mInputEventCounter = new Counter(mUnpluggables);
-        mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables);
+        mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables);
         for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
-            mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables);
+            mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables);
         }
-        mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables);
+        mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables);
         for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
-            mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables);
+            mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables);
         }
-        mWifiOnTimer = new StopwatchTimer(-3, null, mUnpluggables);
-        mWifiRunningTimer = new StopwatchTimer(-4, null, mUnpluggables);
-        mBluetoothOnTimer = new StopwatchTimer(-5, null, mUnpluggables);
-        mAudioOnTimer = new StopwatchTimer(-6, null, mUnpluggables);
-        mVideoOnTimer = new StopwatchTimer(-7, null, mUnpluggables);
+        mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables);
+        mWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables);
+        mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables);
+        mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables);
+        mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables);
         mOnBattery = mOnBatteryInternal = false;
         initTimes();
         mTrackBatteryPastUptime = 0;
@@ -3567,9 +3729,14 @@
 
     public BatteryStatsImpl(Parcel p) {
         mFile = null;
+        mHandler = null;
         readFromParcel(p);
     }
 
+    public void setCallback(BatteryCallback cb) {
+        mCallback = cb;
+    }
+
     public void setNumSpeedSteps(int steps) {
         if (sNumSpeedSteps == 0) sNumSpeedSteps = steps;
     }
@@ -3586,6 +3753,11 @@
     }
     
     @Override
+    public long getHistoryBaseTime() {
+        return mHistoryBaseTime;
+    }
+    
+    @Override
     public int getStartCount() {
         return mStartCount;
     }
@@ -3649,6 +3821,9 @@
     void setOnBattery(boolean onBattery, int oldStatus, int level) {
         synchronized(this) {
             boolean doWrite = false;
+            Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
+            m.arg1 = onBattery ? 1 : 0;
+            mHandler.sendMessage(m);
             mOnBattery = mOnBatteryInternal = onBattery;
             
             long uptime = SystemClock.uptimeMillis() * 1000;
@@ -4549,26 +4724,29 @@
         mBatteryRealtime = in.readLong();
         mBatteryLastRealtime = 0;
         mScreenOn = false;
-        mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables, in);
+        mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
-            mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables, in);
+            mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i,
+                    null, mUnpluggables, in);
         }
         mInputEventCounter = new Counter(mUnpluggables, in);
         mPhoneOn = false;
-        mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+        mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
         for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
-            mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables, in);
+            mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i,
+                    null, mUnpluggables, in);
         }
-        mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables, in);
+        mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in);
         for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
-            mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables, in);
+            mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i,
+                    null, mUnpluggables, in);
         }
         mWifiOn = false;
-        mWifiOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+        mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
         mWifiRunning = false;
-        mWifiRunningTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+        mWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
         mBluetoothOn = false;
-        mBluetoothOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+        mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
         mUptime = in.readLong();
         mUptimeStart = in.readLong();
         mLastUptime = 0;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 42135c5..a038cc5 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -92,7 +92,6 @@
 	android/graphics/MaskFilter.cpp \
 	android/graphics/Matrix.cpp \
 	android/graphics/Movie.cpp \
-	android/graphics/NIOBuffer.cpp \
 	android/graphics/NinePatch.cpp \
 	android/graphics/NinePatchImpl.cpp \
 	android/graphics/Paint.cpp \
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 578de6f..72cea65 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -2,7 +2,6 @@
 
 #include "jni.h"
 #include "GraphicsJNI.h"
-#include "NIOBuffer.h"
 #include "SkPicture.h"
 #include "SkRegion.h"
 #include <android_runtime/AndroidRuntime.h>
@@ -650,8 +649,6 @@
     gVMRuntime_trackExternalFreeMethodID =
                             env->GetMethodID(c, "trackExternalFree", "(J)V");
 
-    NIOBuffer::RegisterJNI(env);
-
     return 0;
 }
 
diff --git a/core/jni/android/graphics/NIOBuffer.cpp b/core/jni/android/graphics/NIOBuffer.cpp
deleted file mode 100644
index cb937a3..0000000
--- a/core/jni/android/graphics/NIOBuffer.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-#include "NIOBuffer.h"
-#include "GraphicsJNI.h"
-
-// enable this to dump each time we ref/unref a global java object (buffer)
-//
-//#define TRACE_GLOBAL_REFS
-
-//#define TRACE_ARRAY_LOCKS
-
-static jclass gNIOAccess_classID;
-static jmethodID gNIOAccess_getBasePointer;
-static jmethodID gNIOAccess_getBaseArray;
-static jmethodID gNIOAccess_getBaseArrayOffset;
-static jmethodID gNIOAccess_getRemainingBytes;
-
-void NIOBuffer::RegisterJNI(JNIEnv* env) {
-    if (0 != gNIOAccess_classID) {
-        return; // already called
-    }
-
-    jclass c = env->FindClass("java/nio/NIOAccess");
-    gNIOAccess_classID = (jclass)env->NewGlobalRef(c);
-
-    gNIOAccess_getBasePointer = env->GetStaticMethodID(gNIOAccess_classID,
-                                    "getBasePointer", "(Ljava/nio/Buffer;)J");
-    gNIOAccess_getBaseArray = env->GetStaticMethodID(gNIOAccess_classID,
-                    "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
-    gNIOAccess_getBaseArrayOffset = env->GetStaticMethodID(gNIOAccess_classID,
-                                "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
-    gNIOAccess_getRemainingBytes = env->GetStaticMethodID(gNIOAccess_classID,
-                                "getRemainingBytes", "(Ljava/nio/Buffer;)I");
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-#ifdef TRACE_GLOBAL_REFS
-    static int gGlobalRefs;
-#endif
-
-#ifdef TRACE_ARRAY_LOCKS
-    static int gLockCount;
-#endif
-
-NIOBuffer::NIOBuffer(JNIEnv* env, jobject buffer) {
-    fBuffer = env->NewGlobalRef(buffer);
-#ifdef TRACE_GLOBAL_REFS
-    SkDebugf("------------ newglobalref bbuffer %X %d\n", buffer, gGlobalRefs++);
-#endif
-    fLockedPtr = NULL;
-    fLockedArray = NULL;
-}
-
-NIOBuffer::~NIOBuffer() {
-    // free() needs to have already been called
-    if (NULL != fBuffer) {
-        SkDebugf("----- leaked fBuffer in NIOBuffer");
-        sk_throw();
-    }
-}
-
-void NIOBuffer::free(JNIEnv* env) {
-
-    if (NULL != fLockedPtr) {
-        SkDebugf("======= free: array still locked %x %p\n", fLockedArray, fLockedPtr);
-    }
-    
-    
-    if (NULL != fBuffer) {
-#ifdef TRACE_GLOBAL_REFS
-        SkDebugf("----------- deleteglobalref buffer %X %d\n", fBuffer, --gGlobalRefs);
-#endif
-        env->DeleteGlobalRef(fBuffer);
-        fBuffer = NULL;
-    }
-}
-
-void* NIOBuffer::lock(JNIEnv* env, int* remaining) {
-    if (NULL != fLockedPtr) {
-        SkDebugf("======= lock: array still locked %x %p\n", fLockedArray, fLockedPtr);
-    }
-
-    fLockedPtr = NULL;
-    fLockedArray = NULL;
-
-    if (NULL != remaining) {
-        *remaining = env->CallStaticIntMethod(gNIOAccess_classID,
-                                              gNIOAccess_getRemainingBytes,
-                                              fBuffer);
-        if (GraphicsJNI::hasException(env)) {
-            return NULL;
-        }
-    }
-    
-    jlong pointer = env->CallStaticLongMethod(gNIOAccess_classID,
-                                              gNIOAccess_getBasePointer,
-                                              fBuffer);
-    if (GraphicsJNI::hasException(env)) {
-        return NULL;
-    }
-    if (0 != pointer) {
-        return reinterpret_cast<void*>(pointer);
-    }
-    
-    fLockedArray = (jbyteArray)env->CallStaticObjectMethod(gNIOAccess_classID,
-                                                        gNIOAccess_getBaseArray,
-                                                        fBuffer);
-    if (GraphicsJNI::hasException(env) || NULL == fLockedArray) {
-        return NULL;
-    }
-    jint offset = env->CallStaticIntMethod(gNIOAccess_classID,
-                                           gNIOAccess_getBaseArrayOffset,
-                                           fBuffer);
-    fLockedPtr = env->GetByteArrayElements(fLockedArray, NULL);
-    if (GraphicsJNI::hasException(env)) {
-        SkDebugf("------------ failed to lockarray %x\n", fLockedArray);
-        return NULL;
-    }
-#ifdef TRACE_ARRAY_LOCKS
-    SkDebugf("------------ lockarray %x %p %d\n",
-             fLockedArray, fLockedPtr, gLockCount++);
-#endif
-    if (NULL == fLockedPtr) {
-        offset = 0;
-    }
-    return (char*)fLockedPtr + offset;
-}
-
-void NIOBuffer::unlock(JNIEnv* env, bool dataChanged) {
-    if (NULL != fLockedPtr) {
-#ifdef TRACE_ARRAY_LOCKS
-        SkDebugf("------------ unlockarray %x %p %d\n",
-                 fLockedArray, fLockedPtr, --gLockCount);
-#endif
-        env->ReleaseByteArrayElements(fLockedArray, (jbyte*)fLockedPtr,
-                                      dataChanged ? 0 : JNI_ABORT);
-        
-        fLockedPtr = NULL;
-        fLockedArray = NULL;
-    } else {
-        SkDebugf("============= unlock called with null ptr %x\n", fLockedArray);
-    }
-}
-
diff --git a/core/jni/android/graphics/NIOBuffer.h b/core/jni/android/graphics/NIOBuffer.h
deleted file mode 100644
index 36b5554..0000000
--- a/core/jni/android/graphics/NIOBuffer.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef NIOBuffer_DEFINED
-#define NIOBuffer_DEFINED
-
-#include <jni.h>
-#include "SkBitmap.h"
-
-class NIOBuffer {
-public:
-    NIOBuffer(JNIEnv* env, jobject buffer);
-    // this checks to ensure that free() was called
-    ~NIOBuffer();
-
-    void* lock(JNIEnv* env, int* remaining);
-    void unlock(JNIEnv* env, bool dataChanged);
-    // must be called before destructor
-    void free(JNIEnv* env);
-
-    // call once on boot, to setup JNI globals
-    static void RegisterJNI(JNIEnv*);
-
-private:
-    jobject     fBuffer;
-    void*       fLockedPtr;
-    jbyteArray  fLockedArray;
-};
-
-#endif
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 0932473a..2517a8a 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -127,12 +127,13 @@
     close(mDispatchKeyWrite);
 }
 
-void AInputQueue::attachLooper(ALooper* looper, ALooper_callbackFunc* callback, void* data) {
+void AInputQueue::attachLooper(ALooper* looper, int ident,
+        ALooper_callbackFunc* callback, void* data) {
     mPollLoop = static_cast<android::PollLoop*>(looper);
     mPollLoop->setLooperCallback(mConsumer.getChannel()->getReceivePipeFd(),
-            POLLIN, callback, data);
+            ident, POLLIN, callback, data);
     mPollLoop->setLooperCallback(mDispatchKeyRead,
-            POLLIN, callback, data);
+            ident, POLLIN, callback, data);
 }
 
 void AInputQueue::detachLooper() {
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 903283e..fb029e6 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -497,7 +497,7 @@
 {
     char cmdstr[25];
 
-    snprintf(cmdstr, sizeof(cmdstr), "DRIVER SETSUSPEND %d", enabled ? 0 : 1);
+    snprintf(cmdstr, sizeof(cmdstr), "DRIVER SETSUSPENDOPT %d", enabled ? 0 : 1);
     return doBooleanCommand(cmdstr, "OK");
 }
 
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7c99271..7dfb716 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -261,8 +261,7 @@
             continue;
         }
      
-        if (set_sched_policy(t_pid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
-                                            SP_BACKGROUND : SP_FOREGROUND)) {
+        if (androidSetThreadSchedulingGroup(t_pid, grp) != NO_ERROR) {
             signalExceptionForGroupError(env, clazz, errno);
             break;
         }
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index fe247e8..93fd54f 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -46,6 +46,7 @@
     jfieldID mYPrecision;
     jfieldID mEdgeFlags;
     jfieldID mMetaState;
+    jfieldID mFlags;
     jfieldID mNumPointers;
     jfieldID mNumSamples;
     jfieldID mPointerIdentifiers;
@@ -91,6 +92,8 @@
             event->getEdgeFlags());
     env->SetIntField(eventObj, gMotionEventClassInfo.mMetaState,
             event->getMetaState());
+    env->SetIntField(eventObj, gMotionEventClassInfo.mFlags,
+            event->getFlags());
     env->SetIntField(eventObj, gMotionEventClassInfo.mNumPointers,
             numPointers);
     env->SetIntField(eventObj, gMotionEventClassInfo.mNumSamples,
@@ -162,6 +165,7 @@
     jfloat yPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mYPrecision);
     jint edgeFlags = env->GetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags);
     jint metaState = env->GetIntField(eventObj, gMotionEventClassInfo.mMetaState);
+    jint flags = env->GetIntField(eventObj, gMotionEventClassInfo.mFlags);
     jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers);
     jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples);
     jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj,
@@ -196,7 +200,7 @@
         samplePointerCoords[j].orientation = *(srcDataSamples++);
     }
 
-    event->initialize(deviceId, source, action, edgeFlags, metaState,
+    event->initialize(deviceId, source, action, flags, edgeFlags, metaState,
             xOffset, yOffset, xPrecision, yPrecision, downTimeNano, sampleEventTime,
             numPointers, pointerIdentifiers, samplePointerCoords);
 
@@ -281,6 +285,8 @@
             "mEdgeFlags", "I");
     GET_FIELD_ID(gMotionEventClassInfo.mMetaState, gMotionEventClassInfo.clazz,
             "mMetaState", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mFlags, gMotionEventClassInfo.clazz,
+            "mFlags", "I");
     GET_FIELD_ID(gMotionEventClassInfo.mNumPointers, gMotionEventClassInfo.clazz,
             "mNumPointers", "I");
     GET_FIELD_ID(gMotionEventClassInfo.mNumSamples, gMotionEventClassInfo.clazz,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0323b70..a9e4971 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -50,6 +50,8 @@
     <protected-broadcast android:name="android.intent.action.ACTION_SHUTDOWN" />
     <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_LOW" />
     <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" />
+    <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_FULL" />
+    <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_NOT_FULL" />
     <protected-broadcast android:name="android.intent.action.NEW_OUTGOING_CALL" />
     <protected-broadcast android:name="android.intent.action.REBOOT" />
     <protected-broadcast android:name="android.intent.action.DOCK_EVENT" />
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 7942c56..55d9b6c 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Vybrat vše"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Označit text"</string>
     <string name="cut" msgid="3092569408438626261">"Vyjmout"</string>
     <string name="copy" msgid="2681946229533511987">"Kopírovat"</string>
     <string name="paste" msgid="5629880836805036433">"Vložit"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index bab18f2..9e6865e 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Vælg alle"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Marker tekst"</string>
     <string name="cut" msgid="3092569408438626261">"Klip"</string>
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
     <string name="paste" msgid="5629880836805036433">"Indsæt"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index c20aa16..1e1556f 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Alles auswählen"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Text auswählen"</string>
     <string name="cut" msgid="3092569408438626261">"Ausschneiden"</string>
     <string name="copy" msgid="2681946229533511987">"Kopieren"</string>
     <string name="paste" msgid="5629880836805036433">"Einfügen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ab29601..a0bb65b 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Επιλογή όλων"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Επιλογή κειμένου"</string>
     <string name="cut" msgid="3092569408438626261">"Αποκοπή"</string>
     <string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string>
     <string name="paste" msgid="5629880836805036433">"Επικόλληση"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index fce56cd..b07d79a 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleccionar todos"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Seleccionar texto"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 4fdaf3c..35e19a8 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleccionar todo"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Seleccionar texto"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 9f0fa0b..e826aba 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Tout sélectionner"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Sélectionner le texte"</string>
     <string name="cut" msgid="3092569408438626261">"Couper"</string>
     <string name="copy" msgid="2681946229533511987">"Copier"</string>
     <string name="paste" msgid="5629880836805036433">"Coller"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 4731f39..46b735e 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleziona tutto"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Seleziona testo"</string>
     <string name="cut" msgid="3092569408438626261">"Taglia"</string>
     <string name="copy" msgid="2681946229533511987">"Copia"</string>
     <string name="paste" msgid="5629880836805036433">"Incolla"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index a9613f4..802c6a3 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"すべて選択"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"テキストを選択"</string>
     <string name="cut" msgid="3092569408438626261">"切り取り"</string>
     <string name="copy" msgid="2681946229533511987">"コピー"</string>
     <string name="paste" msgid="5629880836805036433">"貼り付け"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index d6ab5ad..165c5e5 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"모두 선택"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"텍스트 선택"</string>
     <string name="cut" msgid="3092569408438626261">"잘라내기"</string>
     <string name="copy" msgid="2681946229533511987">"복사"</string>
     <string name="paste" msgid="5629880836805036433">"붙여넣기"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 021b20e..b4af7a6 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Merk alt"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Merk tekst"</string>
     <string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
     <string name="paste" msgid="5629880836805036433">"Lim inn"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index a74db2b..8829f277 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Alles selecteren"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Tekst selecteren"</string>
     <string name="cut" msgid="3092569408438626261">"Knippen"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiëren"</string>
     <string name="paste" msgid="5629880836805036433">"Plakken"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index f5ed50c..dd9747d 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Zaznacz wszystko"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Zaznacz tekst"</string>
     <string name="cut" msgid="3092569408438626261">"Wytnij"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiuj"</string>
     <string name="paste" msgid="5629880836805036433">"Wklej"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 7914a3d..cce7ff9 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleccionar tudo"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Seleccionar texto"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index c9c792e..7163fce 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Selecionar tudo"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Selecionar texto"</string>
     <string name="cut" msgid="3092569408438626261">"Recortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 786571b..043e094 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Выбрать все"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Выбрать текст"</string>
     <string name="cut" msgid="3092569408438626261">"Вырезать"</string>
     <string name="copy" msgid="2681946229533511987">"Копировать"</string>
     <string name="paste" msgid="5629880836805036433">"Вставить"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 8a115697..e32746a 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Välj alla"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Markera text"</string>
     <string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiera"</string>
     <string name="paste" msgid="5629880836805036433">"Klistra in"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index b45d5bb..bd87446 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Tümünü seç"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"Metin seç"</string>
     <string name="cut" msgid="3092569408438626261">"Kes"</string>
     <string name="copy" msgid="2681946229533511987">"Kopyala"</string>
     <string name="paste" msgid="5629880836805036433">"Yapıştır"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 88c38e5..aa1f93f 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"全选"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"选择文字"</string>
     <string name="cut" msgid="3092569408438626261">"剪切"</string>
     <string name="copy" msgid="2681946229533511987">"复制"</string>
     <string name="paste" msgid="5629880836805036433">"粘贴"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 7d3c27a..fdce813 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -703,8 +703,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"全部選取"</string>
-    <!-- no translation found for selectText (4862359311088898878) -->
-    <skip />
+    <!-- outdated translation 3889149123626888637 -->     <string name="selectText" msgid="4862359311088898878">"選取文字"</string>
     <string name="cut" msgid="3092569408438626261">"剪下"</string>
     <string name="copy" msgid="2681946229533511987">"複製"</string>
     <string name="paste" msgid="5629880836805036433">"貼上"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1130b69..13c3e7e 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1274,6 +1274,12 @@
              be saved. -->
         <attr name="saveEnabled" format="boolean" />
 
+        <!-- Specifies whether to filter touches when the view's window is obscured by
+             another visible window.  When set to true, the view will not receive touches
+             whenever a toast, dialog or other window appears above the view's window.
+             Refer to the {@link android.view.View} security documentation for more details. -->
+        <attr name="filterTouchesWhenObscured" format="boolean" />
+
         <!-- Defines the quality of translucent drawing caches. This property is used
              only when the drawing cache is enabled and translucent. The default value is auto. -->
         <attr name="drawingCacheQuality">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index de419be..28a7cca 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1254,6 +1254,7 @@
   <public type="attr" name="overscrollMode" id="0x010102c1" />
   <public type="attr" name="overscrollHeader" id="0x010102c2" />
   <public type="attr" name="overscrollFooter" id="0x010102c3" />
+  <public type="attr" name="filterTouchesWhenObscured" id="0x010102c4" />
 
   <public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
   
@@ -1270,7 +1271,13 @@
 
   <public-padding type="drawable" name="kraken_resource_pad" end="0x01080100" />
   
+  <public type="style" name="TextAppearance.StatusBar.Title" id="0x01030065" />
+  <public type="style" name="TextAppearance.StatusBar.Icon" id="0x01030066" />
+  <public type="style" name="TextAppearance.StatusBar.EventContent" id="0x01030067" />
+  <public type="style" name="TextAppearance.StatusBar.EventContent.Title" id="0x01030068" />
+
   <public-padding type="style" name="kraken_resource_pad" end="0x01030090" />
+
   <public-padding type="string" name="kraken_resource_pad" end="0x01040020" />
   <public-padding type="integer" name="kraken_resource_pad" end="0x010e0010" />
   <public-padding type="layout" name="kraken_resource_pad" end="0x01090020" />
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 91cbe2f..fed39c9 100644
--- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
@@ -37,6 +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,12 +120,33 @@
 
     /**
      * 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
+     * @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
      * @return the {@link CollectingTestRunListener}
      */
-    private CollectingTestRunListener doRunTests(String pkgName) throws IOException {
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, mDevice);
+    private CollectingTestRunListener doRunTests(String pkgName, String className, String
+            methodName, String runnerName, Map<String, String> params) throws IOException {
+
+        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();
         testRunner.run(listener);
         return listener;
@@ -132,10 +156,27 @@
      * Runs the specified packages tests, and returns whether all tests passed or not.
      *
      * @param pkgName Android application package for tests
+     * @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 {
+        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
      * @return true if every test passed, false otherwise.
      */
     public boolean runDeviceTestsDidAllTestsPass(String pkgName) throws IOException {
-        CollectingTestRunListener listener = doRunTests(pkgName);
+        CollectingTestRunListener listener = doRunTests(pkgName, null, null, null, null);
         return listener.didAllTestsPass();
     }
 
@@ -452,7 +493,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..ed280c9
--- /dev/null
+++ b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java
@@ -0,0 +1,201 @@
+/*
+ * 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 android.content.pm.PackageManagerHostTestUtils.CollectingTestRunListener;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.SyncService;
+import com.android.ddmlib.SyncService.ISyncProgressMonitor;
+import com.android.ddmlib.SyncService.SyncResult;
+import com.android.hosttest.DeviceTestCase;
+import com.android.hosttest.DeviceTestSuite;
+
+import java.io.File;
+import java.io.IOException;
+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/practices/design/performance.jd b/docs/html/guide/practices/design/performance.jd
index f22d2d3..56872a7 100644
--- a/docs/html/guide/practices/design/performance.jd
+++ b/docs/html/guide/practices/design/performance.jd
@@ -42,188 +42,39 @@
 
 <h2 id="optimize_judiciously">Optimize Judiciously</h2>
 
-<p>As you get started thinking about how to design your application, and as
-you write it, consider
-the cautionary points about optimization that Josh Bloch makes in his book
-<em>Effective Java</em>. Here's "Item 47: Optimize Judiciously", excerpted from
-the latest edition of the book with permission. Although Josh didn't have
-Android application development in mind when writing this section &mdash; for
-example, the <code style="color:black">java.awt.Component</code> class
-referenced is not available in Android, and Android uses the
-Dalvik VM, rather than a standard JVM &mdash; his points are still valid. </p>
+<p>This document is about Android-specific micro-optimization, so it assumes
+that you've already used profiling to work out exactly what code needs to be
+optimized, and that you already have a way to measure the effect (good or bad)
+of any changes you make. You only have so much engineering time to invest, so
+it's important to know you're spending it wisely.
 
-<blockquote>
+<p>(See <a href="#closing_notes">Closing Notes</a> for more on profiling and
+writing effective benchmarks.)
 
-<p>There are three aphorisms concerning optimization that everyone should know.
-They are perhaps beginning to suffer from overexposure, but in case you aren't
-yet familiar with them, here they are:</p>
+<p>This document also assumes that you made the best decisions about data
+structures and algorithms, and that you've also considered the future
+performance consequences of your API decisions. Using the right data
+structures and algorithms will make more difference than any of the advice
+here, and considering the performance consequences of your API decisions will
+make it easier to switch to better implementations later (this is more
+important for library code than for application code).
 
-<div style="padding-left:3em;padding-right:4em;">
+<p>(If you need that kind of advice, see Josh Bloch's <em>Effective Java</em>,
+item 47.)</p>
 
-<p style="margin-bottom:.5em;">More computing sins are committed in the name of
-efficiency (without necessarily achieving it) than for any other single
-reason&mdash;including blind stupidity.</p>
-<p>&mdash;William A. Wulf <span style="font-size:80%;"><sup>1</sup></span></p>
+<p>One of the trickiest problems you'll face when micro-optimizing an Android
+app is that your app is pretty much guaranteed to be running on multiple
+hardware platforms. Different versions of the VM running on different
+processors running at different speeds. It's not even generally the case
+that you can simply say "device X is a factor F faster/slower than device Y",
+and scale your results from one device to others. In particular, measurement
+on the emulator tells you very little about performance on any device. There
+are also huge differences between devices with and without a JIT: the "best"
+code for a device with a JIT is not always the best code for a device
+without.</p>
 
-<p style="margin-bottom:.5em;">We should forget about small efficiencies, say
-about 97% of the time: premature optimization is the root of all evil. </p>
-<p>&mdash;Donald E. Knuth <span style="font-size:80%;"><sup>2</sup></span></p>
-
-
-<p style="margin-bottom:.5em;">We follow two rules in the matter of optimization:</p>
-<ul style="margin-bottom:0">
-<li>Rule 1. Don't do it.</li>
-<li>Rule 2 (for experts only). Don't do it yet &mdash; that is, not until you have a
-perfectly clear and unoptimized solution. </li>
-</ul>
-<p>&mdash;M. A. Jackson <span style="font-size:80%;"><sup>3</sup></span></p>
-</div>
-
-<p>All of these aphorisms predate the Java programming language by two decades.
-They tell a deep truth about optimization: it is easy to do more harm than good,
-especially if you optimize prematurely. In the process, you may produce software
-that is neither fast nor correct and cannot easily be fixed.</p>
-
-<p>Don't sacrifice sound architectural principles for performance.
-<strong>Strive to write good programs rather than fast ones.</strong> If a good
-program is not fast enough, its architecture will allow it to be optimized. Good
-programs embody the principle of <em>information hiding</em>: where possible,
-they localize design decisions within individual modules, so individual
-decisions can be changed without affecting the remainder of the system (Item
-13).</p>
-
-<p>This does <em>not</em> mean that you can ignore performance concerns until
-your program is complete. Implementation problems can be fixed by later
-optimization, but pervasive architectural flaws that limit performance can be
-impossible to fix without rewriting the system. Changing a fundamental facet of
-your design after the fact can result in an ill-structured system that is
-difficult to maintain and evolve. Therefore you must think about performance
-during the design process.</p>
-
-<p><strong>Strive to avoid design decisions that limit performance.</strong> The
-components of a design that are most difficult to change after the fact are
-those specifying interactions between modules and with the outside world. Chief
-among these design components are APIs, wire-level protocols, and persistent
-data formats. Not only are these design components difficult or impossible to
-change after the fact, but all of them can place significant limitations on the
-performance that a system can ever achieve.</p>
-
-<p><strong>Consider the performance consequences of your API design
-decisions.</strong> Making a public type mutable may require a lot of needless
-defensive copying (Item 39). Similarly, using inheritance in a public class
-where composition would have been appropriate ties the class forever to its
-superclass, which can place artificial limits on the performance of the subclass
-(Item 16). As a final example, using an implementation type rather than an
-interface in an API ties you to a specific implementation, even though faster
-implementations may be written in the future (Item 52).</p>
-
-<p>The effects of API design on performance are very real. Consider the <code
-style="color:black">getSize</code> method in the <code
-style="color:black">java.awt.Component</code> class. The decision that this
-performance-critical method was to return a <code
-style="color:black">Dimension</code> instance, coupled with the decision that
-<code style="color:black">Dimension</code> instances are mutable, forces any
-implementation of this method to allocate a new <code
-style="color:black">Dimension</code> instance on every invocation. Even though
-allocating small objects is inexpensive on a modern VM, allocating millions of
-objects needlessly can do real harm to performance.</p>
-
-<p>In this case, several alternatives existed. Ideally, <code
-style="color:black">Dimension</code> should have been immutable (Item 15);
-alternatively, the <code style="color:black">getSize</code> method could have
-been replaced by two methods returning the individual primitive components of a
-<code style="color:black">Dimension</code> object. In fact, two such methods
-were added to the Component API in the 1.2 release for performance reasons.
-Preexisting client code, however, still uses the <code
-style="color:black">getSize</code> method and still suffers the performance
-consequences of the original API design decisions.</p>
-
-<p>Luckily, it is generally the case that good API design is consistent with
-good performance. <strong>It is a very bad idea to warp an API to achieve good
-performance.</strong> The performance issue that caused you to warp the API may
-go away in a future release of the platform or other underlying software, but
-the warped API and the support headaches that come with it will be with you for
-life.</p>
-
-<p>Once you've carefully designed your program and produced a clear, concise,
-and well-structured implementation, <em>then</em> it may be time to consider
-optimization, assuming you're not already satisfied with the performance of the
-program.</p>
-
-<p>Recall that Jackson's two rules of optimization were "Don't do it," and "(for
-experts only). Don't do it yet." He could have added one more: <strong>measure
-performance before and after each attempted optimization.</strong> You may be
-surprised by what you find. Often, attempted optimizations have no measurable
-effect on performance; sometimes, they make it worse. The main reason is that
-it's difficult to guess where your program is spending its time. The part of the
-program that you think is slow may not be at fault, in which case you'd be
-wasting your time trying to optimize it. Common wisdom says that programs spend
-80 percent of their time in 20 percent of their code.</p>
-
-<p>Profiling tools can help you decide where to focus your optimization efforts.
-Such tools give you runtime information, such as roughly how much time each
-method is consuming and how many times it is invoked. In addition to focusing
-your tuning efforts, this can alert you to the need for algorithmic changes. If
-a quadratic (or worse) algorithm lurks inside your program, no amount of tuning
-will fix the problem. You must replace the algorithm with one that is more
-efficient. The more code in the system, the more important it is to use a
-profiler. It's like looking for a needle in a haystack: the bigger the haystack,
-the more useful it is to have a metal detector. The JDK comes with a simple
-profiler and modern IDEs provide more sophisticated profiling tools.</p>
-
-<p>The need to measure the effects of attempted optimization is even greater on
-the Java platform than on more traditional platforms, because the Java
-programming language does not have a strong <em>performance model</em>. The
-relative costs of the various primitive operations are not well defined. The
-"semantic gap" between what the programmer writes and what the CPU executes is
-far greater than in traditional statically compiled languages, which makes it
-very difficult to reliably predict the performance consequences of any
-optimization. There are plenty of performance myths floating around that turn
-out to be half-truths or outright lies.</p>
-
-<p>Not only is Java's performance model ill-defined, but it varies from JVM
-implementation to JVM implementation, from release to release, and from
-processor to processor. If you will be running your program on multiple JVM
-implementations or multiple hardware platforms, it is important that you measure
-the effects of your optimization on each. Occasionally you may be forced to make
-trade-offs between performance on different JVM implementations or hardware
-platforms.</p>
-
-<p>To summarize, do not strive to write fast programs &mdash; strive to write
-good ones; speed will follow. Do think about performance issues while you're
-designing systems and especially while you're designing APIs, wire-level
-protocols, and persistent data formats. When you've finished building the
-system, measure its performance. If it's fast enough, you're done. If not,
-locate the source of the problems with the aid of a profiler, and go to work
-optimizing the relevant parts of the system. The first step is to examine your
-choice of algorithms: no amount of low-level optimization can make up for a poor
-choice of algorithm. Repeat this process as necessary, measuring the performance
-after every change, until you're satisfied.</p>
-
-<p>&mdash;Excerpted from Josh Bloch's <em>Effective Java</em>, Second Ed.
-(Addison-Wesley, 2008).</em></p>
-
-<p style="font-size:80%;margin-bottom:0;"><sup>1</sup> Wulf, W. A Case Against
-the GOTO. <em>Proceedings of the 25th ACM National
-Conference</em> 2 (1972): 791–797.</p>
-<p style="font-size:80%;margin-bottom:0;"><sup>2</sup> Knuth, Donald. Structured
-Programming with go to Statements. <em>Computing
-Surveys 6</em> (1974): 261–301.</p>
-<p style="font-size:80%"><sup>3</sup> Jackson, M. A. <em>Principles of Program
-Design</em>, Academic Press, London, 1975.
-ISBN: 0123790506.</p>
-
-</blockquote>
-
-<p>One of the trickiest problems you'll face when micro-optimizing Android
-apps is that the "if you will be running your program on ... multiple hardware
-platforms" clause above is always true. And it's not even generally the case
-that you can say "device X is a factor F faster/slower than device Y".
-This is especially true if one of the devices is the emulator, or one of the
-devices has a JIT. If you want to know how your app performs on a given device,
-you need to test it on that device. Drawing conclusions from the emulator is
-particularly dangerous, as is attempting to compare JIT versus non-JIT
-performance: the performance <em>profiles</em> can differ wildly.</p>
+<p>If you want to know how your app performs on a given device, you need to
+test on that device.</p>
 
 <a name="object_creation"></a>
 <h2>Avoid Creating Objects</h2>
@@ -566,3 +417,11 @@
 not measuring what you think you're measuring (because, say, the VM has
 managed to optimize all your code away). We highly recommend you use Caliper
 to run your own microbenchmarks.</p>
+
+<p>You may also find
+<a href="{@docRoot}guide/developing/tools/traceview.html">Traceview</a> useful
+for profiling, but it's important to realize that it currently disables the JIT,
+which may cause it to misattribute time to code that the JIT may be able to win
+back. It's especially important after making changes suggested by Traceview
+data to ensure that the resulting code actually runs faster when run without
+Traceview.
diff --git a/include/android_runtime/android_app_NativeActivity.h b/include/android_runtime/android_app_NativeActivity.h
index c388ba8..fdceb84 100644
--- a/include/android_runtime/android_app_NativeActivity.h
+++ b/include/android_runtime/android_app_NativeActivity.h
@@ -69,7 +69,7 @@
     /* Destroys the consumer and releases its input channel. */
     ~AInputQueue();
 
-    void attachLooper(ALooper* looper, ALooper_callbackFunc* callback, void* data);
+    void attachLooper(ALooper* looper, int ident, ALooper_callbackFunc* callback, void* data);
 
     void detachLooper();
 
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 3fa825f..b587e94 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -78,6 +78,11 @@
 
     POLICY_FLAG_RAW_MASK = 0x0000ffff,
 
+    /* These flags are set by the input dispatcher. */
+
+    // Indicates that the input event was injected.
+    POLICY_FLAG_INJECTED = 0x01000000,
+
     /* These flags are set by the input reader policy as it intercepts each event. */
 
     // Indicates that the screen was off when the event was received and the event
@@ -225,6 +230,8 @@
 
     inline int32_t getAction() const { return mAction; }
 
+    inline int32_t getFlags() const { return mFlags; }
+
     inline int32_t getEdgeFlags() const { return mEdgeFlags; }
 
     inline int32_t getMetaState() const { return mMetaState; }
@@ -343,6 +350,7 @@
             int32_t deviceId,
             int32_t source,
             int32_t action,
+            int32_t flags,
             int32_t edgeFlags,
             int32_t metaState,
             float xOffset,
@@ -370,6 +378,7 @@
 
 private:
     int32_t mAction;
+    int32_t mFlags;
     int32_t mEdgeFlags;
     int32_t mMetaState;
     float mXOffset;
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index aed4fa1..f00f2db 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -84,14 +84,22 @@
          * current event is delivered to this target or a timeout occurs. */
         FLAG_SYNC = 0x01,
 
-        /* This flag indicates that a MotionEvent with ACTION_DOWN falls outside of the area of
-         * this target and so should instead be delivered as an ACTION_OUTSIDE to this target. */
+        /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+         * of the area of this target and so should instead be delivered as an
+         * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
         FLAG_OUTSIDE = 0x02,
 
         /* This flag indicates that a KeyEvent or MotionEvent is being canceled.
-         * In the case of a key event, it should be delivered with KeyEvent.FLAG_CANCELED set.
-         * In the case of a motion event, it should be delivered as MotionEvent.ACTION_CANCEL. */
-        FLAG_CANCEL = 0x04
+         * In the case of a key event, it should be delivered with flag
+         * AKEY_EVENT_FLAG_CANCELED set.
+         * In the case of a motion event, it should be delivered with action
+         * AMOTION_EVENT_ACTION_CANCEL instead. */
+        FLAG_CANCEL = 0x04,
+
+        /* This flag indicates that the target of a MotionEvent is partly or wholly
+         * obscured by another visible window above it.  The motion event should be
+         * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+        FLAG_WINDOW_IS_OBSCURED = 0x08,
     };
 
     // The input channel to be targeted.
@@ -139,9 +147,12 @@
     /* Notifies the system that an input channel recovered from ANR. */
     virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) = 0;
 
-    /* Gets the key repeat timeout or -1 if automatic key repeating is disabled. */
+    /* Gets the key repeat initial timeout or -1 if automatic key repeating is disabled. */
     virtual nsecs_t getKeyRepeatTimeout() = 0;
 
+    /* Gets the key repeat inter-key delay. */
+    virtual nsecs_t getKeyRepeatDelay() = 0;
+
     /* Waits for key event input targets to become available.
      * If the event is being injected, injectorPid and injectorUid should specify the
      * process id and used id of the injecting application, otherwise they should both
@@ -193,7 +204,8 @@
             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
             int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
     virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
-            uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
             uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
             float xPrecision, float yPrecision, nsecs_t downTime) = 0;
 
@@ -257,7 +269,8 @@
             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
             int32_t scanCode, int32_t metaState, nsecs_t downTime);
     virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
-            uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
             uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
             float xPrecision, float yPrecision, nsecs_t downTime);
 
@@ -327,6 +340,7 @@
         int32_t source;
         uint32_t policyFlags;
         int32_t action;
+        int32_t flags;
         int32_t metaState;
         int32_t edgeFlags;
         float xPrecision;
@@ -458,7 +472,8 @@
                 int32_t repeatCount, nsecs_t downTime);
         MotionEntry* obtainMotionEntry(nsecs_t eventTime,
                 int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
-                int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
+                int32_t flags, int32_t metaState, int32_t edgeFlags,
+                float xPrecision, float yPrecision,
                 nsecs_t downTime, uint32_t pointerCount,
                 const int32_t* pointerIds, const PointerCoords* pointerCoords);
         DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry);
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
index 31ec701..82831e2 100644
--- a/include/ui/InputTransport.h
+++ b/include/ui/InputTransport.h
@@ -135,6 +135,7 @@
 
         struct {
             int32_t action;
+            int32_t flags;
             int32_t metaState;
             int32_t edgeFlags;
             nsecs_t downTime;
@@ -218,6 +219,7 @@
             int32_t deviceId,
             int32_t source,
             int32_t action,
+            int32_t flags,
             int32_t edgeFlags,
             int32_t metaState,
             float xOffset,
diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h
index 81230e8..bc616eb 100644
--- a/include/utils/PollLoop.h
+++ b/include/utils/PollLoop.h
@@ -111,12 +111,18 @@
      * This method can be called on any thread.
      * This method may block briefly if it needs to wake the poll loop.
      */
-    void setCallback(int fd, int events, Callback callback, void* data = NULL);
+    void setCallback(int fd, int ident, int events, Callback callback, void* data = NULL);
 
     /**
+     * Convenience for above setCallback when ident is not used.  In this case
+     * the ident is set to POLL_CALLBACK.
+     */
+    void setCallback(int fd, int events, Callback callback, void* data = NULL);
+    
+    /**
      * Like setCallback(), but for the NDK callback function.
      */
-    void setLooperCallback(int fd, int events, ALooper_callbackFunc* callback,
+    void setLooperCallback(int fd, int ident, int events, ALooper_callbackFunc* callback,
             void* data);
     
     /**
@@ -153,11 +159,13 @@
     struct RequestedCallback {
         Callback callback;
         ALooper_callbackFunc* looperCallback;
+        int ident;
         void* data;
     };
 
     struct PendingCallback {
         int fd;
+        int ident;
         int events;
         Callback callback;
         ALooper_callbackFunc* looperCallback;
@@ -185,7 +193,7 @@
     void openWakePipe();
     void closeWakePipe();
 
-    void setCallbackCommon(int fd, int events, Callback callback,
+    void setCallbackCommon(int fd, int ident, int events, Callback callback,
             ALooper_callbackFunc* looperCallback, void* data);
     ssize_t getRequestIndexLocked(int fd);
     void wakeAndLock();
diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp
index 3396f25..7eb6da5 100644
--- a/libs/gui/SensorEventQueue.cpp
+++ b/libs/gui/SensorEventQueue.cpp
@@ -86,7 +86,7 @@
     Mutex::Autolock _l(mLock);
     if (mPollLoop == 0) {
         mPollLoop = new PollLoop(true);
-        mPollLoop->setCallback(getFd(), POLLIN, NULL, NULL);
+        mPollLoop->setCallback(getFd(), getFd(), POLLIN, NULL, NULL);
     }
     return mPollLoop;
 }
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index 4973cd8..811edaf 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -129,6 +129,7 @@
         int32_t deviceId,
         int32_t source,
         int32_t action,
+        int32_t flags,
         int32_t edgeFlags,
         int32_t metaState,
         float xOffset,
@@ -142,6 +143,7 @@
         const PointerCoords* pointerCoords) {
     InputEvent::initialize(deviceId, source);
     mAction = action;
+    mFlags = flags;
     mEdgeFlags = edgeFlags;
     mMetaState = metaState;
     mXOffset = xOffset;
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index 886c785..df232d4 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -97,6 +97,7 @@
 
 void InputDispatcher::dispatchOnce() {
     nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
+    nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
 
     bool skipPoll = false;
     nsecs_t currentTime;
@@ -146,7 +147,7 @@
             if (mInboundQueue.isEmpty()) {
                 if (mKeyRepeatState.lastKeyEntry) {
                     if (currentTime >= mKeyRepeatState.nextRepeatTime) {
-                        processKeyRepeatLockedInterruptible(currentTime, keyRepeatTimeout);
+                        processKeyRepeatLockedInterruptible(currentTime, keyRepeatDelay);
                         skipPoll = true;
                     } else {
                         if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
@@ -335,7 +336,7 @@
 }
 
 void InputDispatcher::processKeyRepeatLockedInterruptible(
-        nsecs_t currentTime, nsecs_t keyRepeatTimeout) {
+        nsecs_t currentTime, nsecs_t keyRepeatDelay) {
     KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
 
     // Search the inbound queue for a key up corresponding to this device.
@@ -352,7 +353,7 @@
         }
     }
 
-    // Synthesize a key repeat after the repeat timeout expired.
+    // Synthesize a key repeat.
     // Reuse the repeated key entry if it is otherwise unreferenced.
     uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
     if (entry->refCount == 1) {
@@ -375,7 +376,7 @@
         entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
     }
 
-    mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatTimeout;
+    mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay;
 
 #if DEBUG_OUTBOUND_EVENT_DETAILS
     LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
@@ -392,9 +393,11 @@
 void InputDispatcher::processMotionLockedInterruptible(
         nsecs_t currentTime, MotionEntry* entry) {
 #if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
+    LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, "
             "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
-            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
+            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+            entry->action, entry->flags,
             entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
             entry->downTime);
 
@@ -406,7 +409,7 @@
     }
     for (uint32_t i = 0; i < entry->pointerCount; i++) {
         LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
-                "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
                 "orientation=%f",
                 i, entry->pointerIds[i],
                 sample->pointerCoords[i].x, sample->pointerCoords[i].y,
@@ -465,7 +468,7 @@
     mCurrentInputTargetsValid = false;
     mLock.unlock();
 
-    mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action,
+    mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
             entry->edgeFlags, entry->metaState,
             0, 0, entry->xPrecision, entry->yPrecision,
             entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
@@ -698,12 +701,16 @@
 
         // Apply target flags.
         int32_t action = motionEntry->action;
+        int32_t flags = motionEntry->flags;
         if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
             action = AMOTION_EVENT_ACTION_OUTSIDE;
         }
         if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
             action = AMOTION_EVENT_ACTION_CANCEL;
         }
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+            flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        }
 
         // If headMotionSample is non-NULL, then it points to the first new sample that we
         // were unable to dispatch during the previous cycle so we resume dispatching from
@@ -726,7 +733,7 @@
 
         // Publish the motion event and the first motion sample.
         status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
-                motionEntry->source, action, motionEntry->edgeFlags, motionEntry->metaState,
+                motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
                 xOffset, yOffset,
                 motionEntry->xPrecision, motionEntry->yPrecision,
                 motionEntry->downTime, firstMotionSample->eventTime,
@@ -1073,18 +1080,18 @@
 }
 
 void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
-        uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+        uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,
         uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
         float xPrecision, float yPrecision, nsecs_t downTime) {
 #if DEBUG_INBOUND_EVENT_DETAILS
     LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
-            "action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, "
-            "downTime=%lld",
-            eventTime, deviceId, source, policyFlags, action, metaState, edgeFlags,
+            "action=0x%x, flags=0x%x, metaState=0x%x, edgeFlags=0x%x, "
+            "xPrecision=%f, yPrecision=%f, downTime=%lld",
+            eventTime, deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
             xPrecision, yPrecision, downTime);
     for (uint32_t i = 0; i < pointerCount; i++) {
         LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
-                "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
                 "orientation=%f",
                 i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
                 pointerCoords[i].pressure, pointerCoords[i].size,
@@ -1209,7 +1216,7 @@
 
         // Just enqueue a new motion event.
         MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
-                deviceId, source, policyFlags, action, metaState, edgeFlags,
+                deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
                 xPrecision, yPrecision, downTime,
                 pointerCount, pointerIds, pointerCoords);
 
@@ -1359,7 +1366,7 @@
     switch (event->getType()) {
     case AINPUT_EVENT_TYPE_KEY: {
         const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
-        uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+        uint32_t policyFlags = POLICY_FLAG_INJECTED;
 
         KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
                 keyEvent->getDeviceId(), keyEvent->getSource(), policyFlags,
@@ -1371,7 +1378,7 @@
 
     case AINPUT_EVENT_TYPE_MOTION: {
         const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
-        uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+        uint32_t policyFlags = POLICY_FLAG_INJECTED;
 
         const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
         const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
@@ -1379,7 +1386,8 @@
 
         MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
                 motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
-                motionEvent->getAction(), motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
+                motionEvent->getAction(), motionEvent->getFlags(),
+                motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
                 motionEvent->getXPrecision(), motionEvent->getYPrecision(),
                 motionEvent->getDownTime(), uint32_t(pointerCount),
                 motionEvent->getPointerIds(), samplePointerCoords);
@@ -1664,7 +1672,7 @@
 }
 
 InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
-        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
+        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
         int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
         nsecs_t downTime, uint32_t pointerCount,
         const int32_t* pointerIds, const PointerCoords* pointerCoords) {
@@ -1676,6 +1684,7 @@
     entry->source = source;
     entry->policyFlags = policyFlags;
     entry->action = action;
+    entry->flags = flags;
     entry->metaState = metaState;
     entry->edgeFlags = edgeFlags;
     entry->xPrecision = xPrecision;
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 8ffb48d..d57b38c 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -1153,7 +1153,7 @@
     int32_t pointerId = 0;
 
     getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TRACKBALL, policyFlags,
-            motionEventAction, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
+            motionEventAction, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
             1, & pointerId, pointerCoords, mXPrecision, mYPrecision, downTime);
 }
 
@@ -2324,7 +2324,7 @@
     } // release lock
 
     getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TOUCHSCREEN, policyFlags,
-            motionEventAction, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
+            motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
             pointerCount, pointerIds, pointerCoords,
             xPrecision, yPrecision, mDownTime);
 }
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index cf0f63e..4c402dc 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -318,8 +318,8 @@
         nsecs_t downTime,
         nsecs_t eventTime) {
 #if DEBUG_TRANSPORT_ACTIONS
-    LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, source=%d, "
-            "action=%d, flags=%d, keyCode=%d, scanCode=%d, metaState=%d, repeatCount=%d,"
+    LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, source=0x%x, "
+            "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
             "downTime=%lld, eventTime=%lld",
             mChannel->getName().string(),
             deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
@@ -346,6 +346,7 @@
         int32_t deviceId,
         int32_t source,
         int32_t action,
+        int32_t flags,
         int32_t edgeFlags,
         int32_t metaState,
         float xOffset,
@@ -358,12 +359,12 @@
         const int32_t* pointerIds,
         const PointerCoords* pointerCoords) {
 #if DEBUG_TRANSPORT_ACTIONS
-    LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, source=%d, "
-            "action=%d, edgeFlags=%d, metaState=%d, xOffset=%f, yOffset=%f, "
+    LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, source=0x%x, "
+            "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, xOffset=%f, yOffset=%f, "
             "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, "
             "pointerCount=%d",
             mChannel->getName().string(),
-            deviceId, source, action, edgeFlags, metaState, xOffset, yOffset,
+            deviceId, source, action, flags, edgeFlags, metaState, xOffset, yOffset,
             xPrecision, yPrecision, downTime, eventTime, pointerCount);
 #endif
 
@@ -379,6 +380,7 @@
     }
 
     mSharedMessage->motion.action = action;
+    mSharedMessage->motion.flags = flags;
     mSharedMessage->motion.edgeFlags = edgeFlags;
     mSharedMessage->motion.metaState = metaState;
     mSharedMessage->motion.xOffset = xOffset;
@@ -664,6 +666,7 @@
             mSharedMessage->deviceId,
             mSharedMessage->source,
             mSharedMessage->motion.action,
+            mSharedMessage->motion.flags,
             mSharedMessage->motion.edgeFlags,
             mSharedMessage->motion.metaState,
             mSharedMessage->motion.xOffset,
diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
index 3bc21fa..952b974 100644
--- a/libs/ui/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
@@ -138,6 +138,7 @@
     const int32_t deviceId = 1;
     const int32_t source = AINPUT_SOURCE_TOUCHSCREEN;
     const int32_t action = AMOTION_EVENT_ACTION_MOVE;
+    const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
     const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
     const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
     const float xOffset = -10;
@@ -167,7 +168,7 @@
         }
     }
 
-    status = mPublisher->publishMotionEvent(deviceId, source, action, edgeFlags,
+    status = mPublisher->publishMotionEvent(deviceId, source, action, flags, edgeFlags,
             metaState, xOffset, yOffset, xPrecision, yPrecision,
             downTime, sampleEventTimes[0], pointerCount, pointerIds, samplePointerCoords.array());
     ASSERT_EQ(OK, status)
@@ -213,6 +214,7 @@
     EXPECT_EQ(deviceId, motionEvent->getDeviceId());
     EXPECT_EQ(source, motionEvent->getSource());
     EXPECT_EQ(action, motionEvent->getAction());
+    EXPECT_EQ(flags, motionEvent->getFlags());
     EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags());
     EXPECT_EQ(metaState, motionEvent->getMetaState());
     EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
@@ -322,12 +324,12 @@
     int32_t pointerIds[pointerCount] = { 0 };
     PointerCoords pointerCoords[pointerCount] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status)
             << "publisher publishMotionEvent should return OK";
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(INVALID_OPERATION, status)
             << "publisher publishMotionEvent should return INVALID_OPERATION because ";
@@ -342,7 +344,7 @@
     int32_t pointerIds[pointerCount];
     PointerCoords pointerCoords[pointerCount];
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
@@ -356,7 +358,7 @@
     int32_t pointerIds[pointerCount];
     PointerCoords pointerCoords[pointerCount];
 
-    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
@@ -402,7 +404,7 @@
     PointerCoords pointerCoords[pointerCount];
 
     status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_DOWN,
-            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+            0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status);
 
     status = mPublisher->appendMotionSample(0, pointerCoords);
@@ -419,7 +421,7 @@
     PointerCoords pointerCoords[pointerCount];
 
     status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_MOVE,
-            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+            0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status);
 
     status = mPublisher->sendDispatchSignal();
@@ -446,7 +448,7 @@
     PointerCoords pointerCoords[pointerCount];
 
     status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_MOVE,
-            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+            0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
     ASSERT_EQ(OK, status);
 
     for (int count = 1;; count++) {
diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp
index f740fa0..6d3eeee 100644
--- a/libs/utils/PollLoop.cpp
+++ b/libs/utils/PollLoop.cpp
@@ -95,6 +95,7 @@
     RequestedCallback requestedCallback;
     requestedCallback.callback = NULL;
     requestedCallback.looperCallback = NULL;
+    requestedCallback.ident = 0;
     requestedCallback.data = NULL;
     mRequestedCallbacks.insertAt(requestedCallback, 0);
 }
@@ -116,7 +117,7 @@
         mPendingFdsPos++;
         if (outEvents != NULL) *outEvents = pending.events;
         if (outData != NULL) *outData = pending.data;
-        return pending.fd;
+        return pending.ident;
     }
     
     mLock.lock();
@@ -182,6 +183,7 @@
             const RequestedCallback& requestedCallback = mRequestedCallbacks.itemAt(i);
             PendingCallback pending;
             pending.fd = requestedFd.fd;
+            pending.ident = requestedCallback.ident;
             pending.events = revents;
             pending.callback = requestedCallback.callback;
             pending.looperCallback = requestedCallback.looperCallback;
@@ -191,7 +193,7 @@
                 mPendingCallbacks.push(pending);
             } else if (pending.fd != mWakeReadPipeFd) {
                 if (result == POLL_CALLBACK) {
-                    result = pending.fd;
+                    result = pending.ident;
                     if (outEvents != NULL) *outEvents = pending.events;
                     if (outData != NULL) *outData = pending.data;
                 } else {
@@ -268,16 +270,20 @@
     return mAllowNonCallbacks;
 }
 
+void PollLoop::setCallback(int fd, int ident, int events, Callback callback, void* data) {
+    setCallbackCommon(fd, ident, events, callback, NULL, data);
+}
+
 void PollLoop::setCallback(int fd, int events, Callback callback, void* data) {
-    setCallbackCommon(fd, events, callback, NULL, data);
+    setCallbackCommon(fd, POLL_CALLBACK, events, callback, NULL, data);
 }
 
-void PollLoop::setLooperCallback(int fd, int events, ALooper_callbackFunc* callback,
+void PollLoop::setLooperCallback(int fd, int ident, int events, ALooper_callbackFunc* callback,
         void* data) {
-    setCallbackCommon(fd, events, NULL, callback, data);
+    setCallbackCommon(fd, ident, events, NULL, callback, data);
 }
 
-void PollLoop::setCallbackCommon(int fd, int events, Callback callback,
+void PollLoop::setCallbackCommon(int fd, int ident, int events, Callback callback,
         ALooper_callbackFunc* looperCallback, void* data) {
 
 #if DEBUG_CALLBACKS
@@ -305,6 +311,7 @@
     RequestedCallback requestedCallback;
     requestedCallback.callback = callback;
     requestedCallback.looperCallback = looperCallback;
+    requestedCallback.ident = ident;
     requestedCallback.data = data;
 
     ssize_t index = getRequestIndexLocked(fd);
diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp
index 2b1f490..e5ece8e 100644
--- a/libs/utils/Threads.cpp
+++ b/libs/utils/Threads.cpp
@@ -21,6 +21,7 @@
 #include <utils/Log.h>
 
 #include <cutils/sched_policy.h>
+#include <cutils/properties.h>
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -57,7 +58,7 @@
 // ----------------------------------------------------------------------------
 
 /*
- * Create and run a new thead.
+ * Create and run a new thread.
  *
  * We create it "detached", so it cleans up after itself.
  */
@@ -280,6 +281,22 @@
 #endif
 }
 
+#if defined(HAVE_PTHREADS)
+static pthread_once_t gDoSchedulingGroupOnce = PTHREAD_ONCE_INIT;
+static bool gDoSchedulingGroup = true;
+
+static void checkDoSchedulingGroup(void) {
+    char buf[PROPERTY_VALUE_MAX];
+    int len = property_get("debug.sys.noschedgroups", buf, "");
+    if (len > 0) {
+        int temp;
+        if (sscanf(buf, "%d", &temp) == 1) {
+            gDoSchedulingGroup = temp == 0;
+        }
+    }
+}
+#endif
+
 int androidSetThreadSchedulingGroup(pid_t tid, int grp)
 {
     if (grp > ANDROID_TGROUP_MAX || grp < 0) { 
@@ -287,9 +304,12 @@
     }
 
 #if defined(HAVE_PTHREADS)
-    if (set_sched_policy(tid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
-                                      SP_BACKGROUND : SP_FOREGROUND)) {
-        return PERMISSION_DENIED;
+    pthread_once(&gDoSchedulingGroupOnce, checkDoSchedulingGroup);
+    if (gDoSchedulingGroup) {
+        if (set_sched_policy(tid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
+                                          SP_BACKGROUND : SP_FOREGROUND)) {
+            return PERMISSION_DENIED;
+        }
     }
 #endif
     
@@ -303,10 +323,13 @@
 #if defined(HAVE_PTHREADS)
     int lasterr = 0;
 
-    if (pri >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = set_sched_policy(tid, SP_BACKGROUND);
-    } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = set_sched_policy(tid, SP_FOREGROUND);
+    pthread_once(&gDoSchedulingGroupOnce, checkDoSchedulingGroup);
+    if (gDoSchedulingGroup) {
+        if (pri >= ANDROID_PRIORITY_BACKGROUND) {
+            rc = set_sched_policy(tid, SP_BACKGROUND);
+        } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
+            rc = set_sched_policy(tid, SP_FOREGROUND);
+        }
     }
 
     if (rc) {
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index a0e01c6..2d53136 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -636,7 +636,7 @@
         memcpy(buffer, ptr, uncompLen);
     } else {
         if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
-            goto unmap;
+            goto bail;
     }
 
     if (compLen > kSequentialMin)
@@ -644,8 +644,6 @@
 
     result = true;
 
-unmap:
-    file->release();
 bail:
     return result;
 }
@@ -669,7 +667,7 @@
 
     getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
 
-    FileMap* file = createEntryFileMap(entry);
+    const FileMap* file = createEntryFileMap(entry);
     if (file == NULL) {
         goto bail;
     }
@@ -680,23 +678,21 @@
         ssize_t actual = write(fd, ptr, uncompLen);
         if (actual < 0) {
             LOGE("Write failed: %s\n", strerror(errno));
-            goto unmap;
+            goto bail;
         } else if ((size_t) actual != uncompLen) {
             LOGE("Partial write during uncompress (%zd of %zd)\n",
                 (size_t)actual, (size_t)uncompLen);
-            goto unmap;
+            goto bail;
         } else {
             LOGI("+++ successful write\n");
         }
     } else {
         if (!inflateBuffer(fd, ptr, uncompLen, compLen))
-            goto unmap;
+            goto bail;
     }
 
     result = true;
 
-unmap:
-    file->release();
 bail:
     return result;
 }
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 379960a..57f0072 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -84,6 +84,10 @@
     return static_cast<const MotionEvent*>(motion_event)->getAction();
 }
 
+int32_t AMotionEvent_getFlags(const AInputEvent* motion_event) {
+    return static_cast<const MotionEvent*>(motion_event)->getFlags();
+}
+
 int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event) {
     return static_cast<const MotionEvent*>(motion_event)->getMetaState();
 }
@@ -246,8 +250,8 @@
 
 
 void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
-        ALooper_callbackFunc* callback, void* data) {
-    queue->attachLooper(looper, callback, data);
+        int ident, ALooper_callbackFunc* callback, void* data) {
+    queue->attachLooper(looper, ident, callback, data);
 }
 
 void AInputQueue_detachLooper(AInputQueue* queue) {
diff --git a/native/android/looper.cpp b/native/android/looper.cpp
index 1564c47..0aeed77 100644
--- a/native/android/looper.cpp
+++ b/native/android/looper.cpp
@@ -72,9 +72,9 @@
     static_cast<PollLoop*>(looper)->decStrong((void*)ALooper_acquire);
 }
 
-void ALooper_addFd(ALooper* looper, int fd, int events,
+void ALooper_addFd(ALooper* looper, int fd, int ident, int events,
         ALooper_callbackFunc* callback, void* data) {
-    static_cast<PollLoop*>(looper)->setLooperCallback(fd, events, callback, data);
+    static_cast<PollLoop*>(looper)->setLooperCallback(fd, ident, events, callback, data);
 }
 
 int32_t ALooper_removeFd(ALooper* looper, int fd) {
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index db534e0..cf7635d 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -60,12 +60,12 @@
 }
 
 ASensorEventQueue* ASensorManager_createEventQueue(ASensorManager* manager,
-        ALooper* looper, ALooper_callbackFunc* callback, void* data)
+        ALooper* looper, int ident, ALooper_callbackFunc* callback, void* data)
 {
     sp<SensorEventQueue> queue =
             static_cast<SensorManager*>(manager)->createEventQueue();
     if (queue != 0) {
-        ALooper_addFd(looper, queue->getFd(), POLLIN, callback, data);
+        ALooper_addFd(looper, queue->getFd(), ident, POLLIN, callback, data);
         queue->looper = looper;
         queue->incStrong(manager);
     }
diff --git a/native/include/android/input.h b/native/include/android/input.h
index 418f609..9da122b 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -247,6 +247,22 @@
 };
 
 /*
+ * Motion event flags.
+ */
+enum {
+    /* This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it.  This flag is set to true
+     * even if the event did not directly pass through the obscured area.
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
+};
+
+/*
  * Motion event edge touch flags.
  */
 enum {
@@ -395,6 +411,9 @@
 /* Get the combined motion event action code and pointer index. */
 int32_t AMotionEvent_getAction(const AInputEvent* motion_event);
 
+/* Get the motion event flags. */
+int32_t AMotionEvent_getFlags(const AInputEvent* motion_event);
+
 /* Get the state of any meta / modifier keys that were in effect when the
  * event was generated. */
 int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event);
@@ -623,10 +642,10 @@
 
 /*
  * Add this input queue to a looper for processing.  See
- * ALooper_addFd() for information on the callback and data params.
+ * ALooper_addFd() for information on the ident, callback, and data params.
  */
 void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
-        ALooper_callbackFunc* callback, void* data);
+        int ident, ALooper_callbackFunc* callback, void* data);
 
 /*
  * Remove the input queue from the looper it is currently attached to.
diff --git a/native/include/android/looper.h b/native/include/android/looper.h
index 2917216..287bcd5 100644
--- a/native/include/android/looper.h
+++ b/native/include/android/looper.h
@@ -111,7 +111,7 @@
  *
  * Returns ALOPER_POLL_ERROR if an error occurred.
  *
- * Returns a value >= 0 containing a file descriptor if it has data
+ * Returns a value >= 0 containing an identifier if its file descriptor has data
  * and it has no callback function (requiring the caller here to handle it).
  * In this (and only this) case outEvents and outData will contain the poll
  * events and data associated with the fd.
@@ -145,10 +145,12 @@
  * descriptor was previously added, it is replaced.
  *
  * "fd" is the file descriptor to be added.
+ * "ident" is an identifier for this event, which is returned from
+ * ALooper_pollOnce().  Must be >= 0, or ALOOPER_POLL_CALLBACK if
+ * providing a non-NULL callback.
  * "events" are the poll events to wake up on.  Typically this is POLLIN.
  * "callback" is the function to call when there is an event on the file
  * descriptor.
- * "id" is an identifier to associated with this file descriptor, or 0.
  * "data" is a private data pointer to supply to the callback.
  *
  * There are two main uses of this function:
@@ -156,13 +158,13 @@
  * (1) If "callback" is non-NULL, then
  * this function will be called when there is data on the file descriptor.  It
  * should execute any events it has pending, appropriately reading from the
- * file descriptor.
+ * file descriptor.  The 'ident' is ignored in this case.
  *
- * (2) If "callback" is NULL, the fd will be returned by ALooper_pollOnce
- * when it has data available, requiring the caller to take care of processing
- * it.
+ * (2) If "callback" is NULL, the 'ident' will be returned by ALooper_pollOnce
+ * when its file descriptor has data available, requiring the caller to take
+ * care of processing it.
  */
-void ALooper_addFd(ALooper* looper, int fd, int events,
+void ALooper_addFd(ALooper* looper, int fd, int ident, int events,
         ALooper_callbackFunc* callback, void* data);
 
 /**
diff --git a/native/include/android/sensor.h b/native/include/android/sensor.h
index b4ce024..a102d43 100644
--- a/native/include/android/sensor.h
+++ b/native/include/android/sensor.h
@@ -166,7 +166,7 @@
  * Creates a new sensor event queue and associate it with a looper.
  */
 ASensorEventQueue* ASensorManager_createEventQueue(ASensorManager* manager,
-        ALooper* looper, ALooper_callbackFunc* callback, void* data);
+        ALooper* looper, int ident, ALooper_callbackFunc* callback, void* data);
 
 /*
  * Destroys the event queue and free all resources associated to it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
index 8f2da7a..af736aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
@@ -46,6 +46,7 @@
 import android.os.Message;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.Log;
 import android.view.Display;
@@ -462,7 +463,11 @@
         }
 
         // Restart the ticker if it's still running
-        tick(notification);
+        if (notification.notification.tickerText != null
+                && !TextUtils.equals(notification.notification.tickerText,
+                    oldEntry.notification.notification.tickerText)) {
+            tick(notification);
+        }
 
         // Recalculate the position of the sliding windows and the titles.
         setAreThereNotifications();
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/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java
index 4a0df59..0b1a4a3 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/DeviceStorageMonitorService.java
@@ -69,10 +69,12 @@
     private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
     private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
     private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
+    private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB
     private long mFreeMem;  // on /data
     private long mLastReportedFreeMem;
     private long mLastReportedFreeMemTime;
     private boolean mLowMemFlag=false;
+    private boolean mMemFullFlag=false;
     private Context mContext;
     private ContentResolver mContentResolver;
     private long mTotalMemory;  // on /data
@@ -87,9 +89,13 @@
     private boolean mClearingCache;
     private Intent mStorageLowIntent;
     private Intent mStorageOkIntent;
+    private Intent mStorageFullIntent;
+    private Intent mStorageNotFullIntent;
     private CachePackageDataObserver mClearCacheObserver;
     private static final int _TRUE = 1;
     private static final int _FALSE = 0;
+    private long mMemLowThreshold;
+    private int mMemFullThreshold;
 
     /**
      * This string is used for ServiceManager access to this class.
@@ -103,7 +109,7 @@
     Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            //dont handle an invalid message
+            //don't handle an invalid message
             if (msg.what != DEVICE_MEMORY_WHAT) {
                 Slog.e(TAG, "Will not process invalid message");
                 return;
@@ -184,7 +190,7 @@
         try {
             if (localLOGV) Slog.i(TAG, "Clearing cache");
             IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
-                    freeStorageAndNotify(getMemThreshold(), mClearCacheObserver);
+                    freeStorageAndNotify(mMemLowThreshold, mClearCacheObserver);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
             mClearingCache = false;
@@ -209,8 +215,7 @@
             if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);
 
             //post intent to NotificationManager to display icon if necessary
-            long memThreshold = getMemThreshold();
-            if (mFreeMem < memThreshold) {
+            if (mFreeMem < mMemLowThreshold) {
                 if (!mLowMemFlag) {
                     if (checkCache) {
                         // See if clearing cache helps
@@ -235,6 +240,17 @@
                     mLowMemFlag = false;
                 }
             }
+            if (mFreeMem < mMemFullThreshold) {
+                if (!mMemFullFlag) {
+                    sendFullNotification();
+                    mMemFullFlag = true;
+                }
+            } else {
+                if (mMemFullFlag) {
+                    cancelFullNotification();
+                    mMemFullFlag = false;
+                }
+            }
         }
         if(localLOGV) Slog.i(TAG, "Posting Message again");
         //keep posting messages to itself periodically
@@ -264,6 +280,20 @@
         return mTotalMemory*value;
     }
 
+    /*
+     * just query settings to retrieve the memory full threshold.
+     * Preferred this over using a ContentObserver since Settings.Secure caches the value
+     * any way
+     */
+    private int getMemFullThreshold() {
+        int value = Settings.Secure.getInt(
+                              mContentResolver,
+                              Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+                              DEFAULT_FULL_THRESHOLD_BYTES);
+        if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
+        return value;
+    }
+
     /**
     * Constructor to run service. initializes the disk space threshold value
     * and posts an empty message to kickstart the process.
@@ -283,6 +313,13 @@
         mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
         mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
+        mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
+        mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        // cache storage thresholds
+        mMemLowThreshold = getMemThreshold();
+        mMemFullThreshold = getMemFullThreshold();
         checkMemory(true);
     }
 
@@ -332,6 +369,23 @@
         mContext.sendBroadcast(mStorageOkIntent);
     }
 
+    /**
+     * Send a notification when storage is full.
+     */
+    private final void sendFullNotification() {
+        if(localLOGV) Slog.i(TAG, "Sending memory full notification");
+        mContext.sendStickyBroadcast(mStorageFullIntent);
+    }
+
+    /**
+     * Cancels memory full notification and sends "not full" intent.
+     */
+    private final void cancelFullNotification() {
+        if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
+        mContext.removeStickyBroadcast(mStorageFullIntent);
+        mContext.sendBroadcast(mStorageNotFullIntent);
+    }
+
     public void updateMemory() {
         int callingUid = getCallingUid();
         if(callingUid != Process.SYSTEM_UID) {
diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java
index 8da0cf1..dbc59ef 100644
--- a/services/java/com/android/server/InputWindow.java
+++ b/services/java/com/android/server/InputWindow.java
@@ -34,9 +34,17 @@
     // Dispatching timeout.
     public long dispatchingTimeoutNanos;
     
-    // Window frame position.
+    // Window frame area.
     public int frameLeft;
     public int frameTop;
+    public int frameRight;
+    public int frameBottom;
+    
+    // Window visible frame area.
+    public int visibleFrameLeft;
+    public int visibleFrameTop;
+    public int visibleFrameRight;
+    public int visibleFrameBottom;
     
     // Window touchable area.
     public int touchableAreaLeft;
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 3841f75..0bc9b61 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -5218,6 +5218,14 @@
                 final Rect frame = child.mFrame;
                 inputWindow.frameLeft = frame.left;
                 inputWindow.frameTop = frame.top;
+                inputWindow.frameRight = frame.right;
+                inputWindow.frameBottom = frame.bottom;
+                
+                final Rect visibleFrame = child.mVisibleFrame;
+                inputWindow.visibleFrameLeft = visibleFrame.left;
+                inputWindow.visibleFrameTop = visibleFrame.top;
+                inputWindow.visibleFrameRight = visibleFrame.right;
+                inputWindow.visibleFrameBottom = visibleFrame.bottom;
                 
                 switch (child.mTouchableInsets) {
                     default:
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 89a1627..3142ee4 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -131,7 +131,8 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
-public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor {
+public final class ActivityManagerService extends ActivityManagerNative
+        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
     static final String TAG = "ActivityManager";
     static final boolean DEBUG = false;
     static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
@@ -750,6 +751,7 @@
     boolean mBooting = false;
     boolean mWaitingUpdate = false;
     boolean mDidUpdate = false;
+    boolean mOnBattery = false;
 
     Context mContext;
 
@@ -1381,8 +1383,10 @@
                 systemDir, "batterystats.bin").toString());
         mBatteryStatsService.getActiveStatistics().readLocked();
         mBatteryStatsService.getActiveStatistics().writeLocked();
+        mOnBattery = mBatteryStatsService.getActiveStatistics().getIsOnBattery();
+        mBatteryStatsService.getActiveStatistics().setCallback(this);
         
-        mUsageStatsService = new UsageStatsService( new File(
+        mUsageStatsService = new UsageStatsService(new File(
                 systemDir, "usagestats").toString());
 
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
@@ -1495,25 +1499,36 @@
             synchronized(bstats) {
                 synchronized(mPidsSelfLocked) {
                     if (haveNewCpuStats) {
-                        if (mBatteryStatsService.isOnBattery()) {
+                        if (mOnBattery) {
+                            int perc = bstats.startAddingCpuLocked();
+                            int totalUTime = 0;
+                            int totalSTime = 0;
                             final int N = mProcessStats.countWorkingStats();
                             for (int i=0; i<N; i++) {
                                 ProcessStats.Stats st
                                         = mProcessStats.getWorkingStats(i);
                                 ProcessRecord pr = mPidsSelfLocked.get(st.pid);
+                                int otherUTime = (st.rel_utime*perc)/100;
+                                int otherSTime = (st.rel_stime*perc)/100;
+                                totalUTime += otherUTime;
+                                totalSTime += otherSTime;
                                 if (pr != null) {
                                     BatteryStatsImpl.Uid.Proc ps = pr.batteryStats;
-                                    ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
+                                    ps.addCpuTimeLocked(st.rel_utime-otherUTime,
+                                            st.rel_stime-otherSTime);
                                     ps.addSpeedStepTimes(cpuSpeedTimes);
                                 } else {
                                     BatteryStatsImpl.Uid.Proc ps =
                                             bstats.getProcessStatsLocked(st.name, st.pid);
                                     if (ps != null) {
-                                        ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
+                                        ps.addCpuTimeLocked(st.rel_utime-otherUTime,
+                                                st.rel_stime-otherSTime);
                                         ps.addSpeedStepTimes(cpuSpeedTimes);
                                     }
                                 }
                             }
+                            bstats.finishAddingCpuLocked(perc, totalUTime,
+                                    totalSTime, cpuSpeedTimes);
                         }
                     }
                 }
@@ -1526,6 +1541,23 @@
         }
     }
     
+    @Override
+    public void batteryNeedsCpuUpdate() {
+        updateCpuStatsNow();
+    }
+
+    @Override
+    public void batteryPowerChanged(boolean onBattery) {
+        // When plugging in, update the CPU stats first before changing
+        // the plug state.
+        updateCpuStatsNow();
+        synchronized (this) {
+            synchronized(mPidsSelfLocked) {
+                mOnBattery = onBattery;
+            }
+        }
+    }
+
     /**
      * Initialize the application bind args. These are passed to each
      * process when the bindApplication() IPC is sent to the process. They're
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 6d1fbab..67df707 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -131,6 +131,13 @@
     void dump(PrintWriter pw, String prefix) {
         final long now = SystemClock.uptimeMillis();
 
+        long wtime;
+        synchronized (batteryStats.getBatteryStats()) {
+            wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid,
+                    pid, SystemClock.elapsedRealtime());
+        }
+        long timeUsed = wtime - lastWakeTime;
+
         if (info.className != null) {
             pw.print(prefix); pw.print("class="); pw.println(info.className);
         }
@@ -182,7 +189,9 @@
         pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
                 pw.print(" lruSeq="); pw.println(lruSeq);
         pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
-                pw.print(" lastRequestedGc=");
+                pw.print(" time used=");
+                TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+        pw.print(prefix); pw.print("lastRequestedGc=");
                 TimeUtils.formatDuration(lastRequestedGc, now, pw);
                 pw.print(" lastLowMemory=");
                 TimeUtils.formatDuration(lastLowMemory, now, pw);
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index 8019bfa..70ff9ea 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -123,6 +123,7 @@
         properties.setProperty("javax.sip.STACK_NAME", getStackName());
         String outboundProxy = myself.getProxyAddress();
         if (!TextUtils.isEmpty(outboundProxy)) {
+            Log.v(TAG, "outboundProxy is " + outboundProxy);
             properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy
                     + ":" + myself.getPort() + "/" + myself.getProtocol());
         }
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index a237ee9..7af5e95 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -169,6 +169,12 @@
     jfieldID dispatchingTimeoutNanos;
     jfieldID frameLeft;
     jfieldID frameTop;
+    jfieldID frameRight;
+    jfieldID frameBottom;
+    jfieldID visibleFrameLeft;
+    jfieldID visibleFrameTop;
+    jfieldID visibleFrameRight;
+    jfieldID visibleFrameBottom;
     jfieldID touchableAreaLeft;
     jfieldID touchableAreaTop;
     jfieldID touchableAreaRight;
@@ -269,6 +275,7 @@
             nsecs_t& outNewTimeout);
     virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel);
     virtual nsecs_t getKeyRepeatTimeout();
+    virtual nsecs_t getKeyRepeatDelay();
     virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
             int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
     virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
@@ -283,6 +290,12 @@
         nsecs_t dispatchingTimeout;
         int32_t frameLeft;
         int32_t frameTop;
+        int32_t frameRight;
+        int32_t frameBottom;
+        int32_t visibleFrameLeft;
+        int32_t visibleFrameTop;
+        int32_t visibleFrameRight;
+        int32_t visibleFrameBottom;
         int32_t touchableAreaLeft;
         int32_t touchableAreaTop;
         int32_t touchableAreaRight;
@@ -294,10 +307,8 @@
         int32_t ownerPid;
         int32_t ownerUid;
 
-        inline bool touchableAreaContainsPoint(int32_t x, int32_t y) {
-            return x >= touchableAreaLeft && x <= touchableAreaRight
-                    && y >= touchableAreaTop && y <= touchableAreaBottom;
-        }
+        bool visibleFrameIntersects(const InputWindow* other) const;
+        bool touchableAreaContainsPoint(int32_t x, int32_t y) const;
     };
 
     struct InputApplication {
@@ -370,9 +381,13 @@
     // Focus tracking for touch.
     bool mTouchDown;
     InputWindow* mTouchedWindow;                   // primary target for current down
+    bool mTouchedWindowIsObscured;                 // true if other windows may obscure the target
     Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets
-
-    Vector<InputWindow*> mTempTouchedOutsideWindows; // temporary outside touch targets
+    struct OutsideTarget {
+        InputWindow* window;
+        bool obscured;
+    };
+    Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets
     Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets
 
     // Focused application.
@@ -391,6 +406,7 @@
     int32_t waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags,
             int32_t injectorPid, int32_t injectorUid,
             Vector<InputTarget>& outTargets, InputWindow*& outTouchedWindow);
+    bool isWindowObscuredLocked(const InputWindow* window);
 
     void releaseTouchedWindowLd();
 
@@ -996,6 +1012,10 @@
     }
 }
 
+nsecs_t NativeInputManager::getKeyRepeatDelay() {
+    return milliseconds_to_nanoseconds(50);
+}
+
 int32_t NativeInputManager::getMaxEventsPerSecond() {
     if (mMaxEventsPerSecond < 0) {
         JNIEnv* env = jniEnv();
@@ -1117,6 +1137,18 @@
                     gInputWindowClassInfo.frameLeft);
             jint frameTop = env->GetIntField(windowObj,
                     gInputWindowClassInfo.frameTop);
+            jint frameRight = env->GetIntField(windowObj,
+                    gInputWindowClassInfo.frameRight);
+            jint frameBottom = env->GetIntField(windowObj,
+                    gInputWindowClassInfo.frameBottom);
+            jint visibleFrameLeft = env->GetIntField(windowObj,
+                    gInputWindowClassInfo.visibleFrameLeft);
+            jint visibleFrameTop = env->GetIntField(windowObj,
+                    gInputWindowClassInfo.visibleFrameTop);
+            jint visibleFrameRight = env->GetIntField(windowObj,
+                    gInputWindowClassInfo.visibleFrameRight);
+            jint visibleFrameBottom = env->GetIntField(windowObj,
+                    gInputWindowClassInfo.visibleFrameBottom);
             jint touchableAreaLeft = env->GetIntField(windowObj,
                     gInputWindowClassInfo.touchableAreaLeft);
             jint touchableAreaTop = env->GetIntField(windowObj,
@@ -1144,6 +1176,12 @@
             outWindow.dispatchingTimeout = dispatchingTimeoutNanos;
             outWindow.frameLeft = frameLeft;
             outWindow.frameTop = frameTop;
+            outWindow.frameRight = frameRight;
+            outWindow.frameBottom = frameBottom;
+            outWindow.visibleFrameLeft = visibleFrameLeft;
+            outWindow.visibleFrameTop = visibleFrameTop;
+            outWindow.visibleFrameRight = visibleFrameRight;
+            outWindow.visibleFrameBottom = visibleFrameBottom;
             outWindow.touchableAreaLeft = touchableAreaLeft;
             outWindow.touchableAreaTop = touchableAreaTop;
             outWindow.touchableAreaRight = touchableAreaRight;
@@ -1417,11 +1455,12 @@
             /* Case 1: ACTION_DOWN */
 
             InputWindow* newTouchedWindow = NULL;
-            mTempTouchedOutsideWindows.clear();
+            mTempTouchedOutsideTargets.clear();
 
             int32_t x = int32_t(motionEvent->getX(0));
             int32_t y = int32_t(motionEvent->getY(0));
             InputWindow* topErrorWindow = NULL;
+            bool obscured = false;
 
             // Traverse windows from front to back to find touched window and outside targets.
             size_t numWindows = mWindows.size();
@@ -1442,13 +1481,17 @@
                         if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
                             if (! screenWasOff || flags & FLAG_TOUCHABLE_WHEN_WAKING) {
                                 newTouchedWindow = window;
+                                obscured = isWindowObscuredLocked(window);
                             }
                             break; // found touched window, exit window loop
                         }
                     }
 
                     if (flags & FLAG_WATCH_OUTSIDE_TOUCH) {
-                        mTempTouchedOutsideWindows.push(window);
+                        OutsideTarget outsideTarget;
+                        outsideTarget.window = window;
+                        outsideTarget.obscured = isWindowObscuredLocked(window);
+                        mTempTouchedOutsideTargets.push(outsideTarget);
                     }
                 }
             }
@@ -1501,6 +1544,7 @@
             releaseTouchedWindowLd();
 
             mTouchedWindow = newTouchedWindow;
+            mTouchedWindowIsObscured = obscured;
 
             if (newTouchedWindow->hasWallpaper) {
                 mTouchedWallpaperWindows.appendVector(mWallpaperWindows);
@@ -1557,21 +1601,31 @@
     if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
         size_t numWallpaperWindows = mTouchedWallpaperWindows.size();
         for (size_t i = 0; i < numWallpaperWindows; i++) {
-            addTarget(mTouchedWallpaperWindows[i], 0, 0, outTargets);
+            addTarget(mTouchedWallpaperWindows[i],
+                    InputTarget::FLAG_WINDOW_IS_OBSCURED, 0, outTargets);
         }
 
-        size_t numOutsideWindows = mTempTouchedOutsideWindows.size();
-        for (size_t i = 0; i < numOutsideWindows; i++) {
-            addTarget(mTempTouchedOutsideWindows[i], InputTarget::FLAG_OUTSIDE, 0, outTargets);
+        size_t numOutsideTargets = mTempTouchedOutsideTargets.size();
+        for (size_t i = 0; i < numOutsideTargets; i++) {
+            const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i];
+            int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
+            if (outsideTarget.obscured) {
+                outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+            }
+            addTarget(outsideTarget.window, outsideTargetFlags, 0, outTargets);
         }
 
-        addTarget(mTouchedWindow, InputTarget::FLAG_SYNC,
+        int32_t targetFlags = InputTarget::FLAG_SYNC;
+        if (mTouchedWindowIsObscured) {
+            targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+        }
+        addTarget(mTouchedWindow, targetFlags,
                 anrTimer.getTimeSpentWaitingForApplication(), outTargets);
         outTouchedWindow = mTouchedWindow;
     } else {
         outTouchedWindow = NULL;
     }
-    mTempTouchedOutsideWindows.clear();
+    mTempTouchedOutsideTargets.clear();
 
     // Check injection permission once and for all.
     if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
@@ -1616,6 +1670,7 @@
 
 void NativeInputManager::releaseTouchedWindowLd() {
     mTouchedWindow = NULL;
+    mTouchedWindowIsObscured = false;
     mTouchedWallpaperWindows.clear();
 }
 
@@ -1661,6 +1716,20 @@
     return true;
 }
 
+bool NativeInputManager::isWindowObscuredLocked(const InputWindow* window) {
+    size_t numWindows = mWindows.size();
+    for (size_t i = 0; i < numWindows; i++) {
+        const InputWindow* other = & mWindows.itemAt(i);
+        if (other == window) {
+            break;
+        }
+        if (other->visible && window->visibleFrameIntersects(other)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 int32_t NativeInputManager::waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
         int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
@@ -1935,12 +2004,17 @@
     for (size_t i = 0; i < mWindows.size(); i++) {
         dump.appendFormat("  windows[%d]: '%s', paused=%d, hasFocus=%d, hasWallpaper=%d, "
                 "visible=%d, flags=0x%08x, type=0x%08x, "
-                "frame=[%d,%d], touchableArea=[%d,%d][%d,%d], "
+                "frame=[%d,%d][%d,%d], "
+                "visibleFrame=[%d,%d][%d,%d], "
+                "touchableArea=[%d,%d][%d,%d], "
                 "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
                 i, mWindows[i].inputChannel->getName().string(),
                 mWindows[i].paused, mWindows[i].hasFocus, mWindows[i].hasWallpaper,
                 mWindows[i].visible, mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType,
                 mWindows[i].frameLeft, mWindows[i].frameTop,
+                mWindows[i].frameRight, mWindows[i].frameBottom,
+                mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop,
+                mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom,
                 mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop,
                 mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom,
                 mWindows[i].ownerPid, mWindows[i].ownerUid,
@@ -1955,6 +2029,20 @@
 
 // ----------------------------------------------------------------------------
 
+bool NativeInputManager::InputWindow::visibleFrameIntersects(const InputWindow* other) const {
+    return visibleFrameRight > other->visibleFrameLeft
+        && visibleFrameLeft < other->visibleFrameRight
+        && visibleFrameBottom > other->visibleFrameTop
+        && visibleFrameTop < other->visibleFrameBottom;
+}
+
+bool NativeInputManager::InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
+    return x >= touchableAreaLeft && x <= touchableAreaRight
+            && y >= touchableAreaTop && y <= touchableAreaBottom;
+}
+
+// ----------------------------------------------------------------------------
+
 NativeInputManager::ANRTimer::ANRTimer() :
         mBudget(APPLICATION), mStartTime(now()), mFrozen(false), mPausedWindow(NULL) {
 }
@@ -2507,6 +2595,24 @@
     GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz,
             "frameTop", "I");
 
+    GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz,
+            "frameRight", "I");
+
+    GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz,
+            "frameBottom", "I");
+
+    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameLeft, gInputWindowClassInfo.clazz,
+            "visibleFrameLeft", "I");
+
+    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameTop, gInputWindowClassInfo.clazz,
+            "visibleFrameTop", "I");
+
+    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameRight, gInputWindowClassInfo.clazz,
+            "visibleFrameRight", "I");
+
+    GET_FIELD_ID(gInputWindowClassInfo.visibleFrameBottom, gInputWindowClassInfo.clazz,
+            "visibleFrameBottom", "I");
+
     GET_FIELD_ID(gInputWindowClassInfo.touchableAreaLeft, gInputWindowClassInfo.clazz,
             "touchableAreaLeft", "I");
 
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index f5c6909..b4a3c95 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -130,7 +130,7 @@
         Uri uri = intent.getData();
         String scheme = uri.getScheme();
 
-        if (scheme.equals("tel")) {
+        if (scheme.equals("tel") || scheme.equals("sip")) {
             return uri.getSchemeSpecificPart();
         }
 
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/IccSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 8a5a6ae..5fef6de 100644
--- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -57,7 +57,7 @@
      * @param destPort the port to deliver the message to
      * @param data the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
+     *  broadcast when the message is successfully sent, or failed.
      *  The result code will be <code>Activity.RESULT_OK<code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -67,7 +67,7 @@
      *  the extra "errorCode" containing a radio technology specific value,
      *  generally only useful for troubleshooting.<br>
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is delivered to the recipient.  The
@@ -94,7 +94,7 @@
      *  the current default SMSC
      * @param text the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
+     *  broadcast when the message is successfully sent, or failed.
      *  The result code will be <code>Activity.RESULT_OK<code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -140,7 +140,7 @@
      *   <code>RESULT_ERROR_RADIO_OFF</code>
      *   <code>RESULT_ERROR_NULL_PDU</code>.
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntents if not null, an <code>ArrayList</code> of
      *   <code>PendingIntent</code>s (one for each message part) that is
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index 606b52d..917e1d8 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -32,9 +32,11 @@
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.AsyncResult;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.StatFs;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.provider.Settings;
@@ -240,11 +242,9 @@
 
         // Register for device storage intents.  Use these to notify the RIL
         // that storage for SMS is or is not available.
-        // TODO: Revisit this for a later release.  Storage reporting should
-        // rely more on application indication.
         IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
-        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+        filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL);
+        filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
         mContext.registerReceiver(mResultReceiver, filter);
     }
 
@@ -679,7 +679,7 @@
      *  the extra "errorCode" containing a radio technology specific value,
      *  generally only useful for troubleshooting.<br>
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is delivered to the recipient.  The
@@ -732,7 +732,7 @@
      *   <code>RESULT_ERROR_RADIO_OFF</code>
      *   <code>RESULT_ERROR_NULL_PDU</code>.
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntents if not null, an <code>ArrayList</code> of
      *   <code>PendingIntent</code>s (one for each message part) that is
@@ -748,7 +748,7 @@
      * Send a SMS
      *
      * @param smsc the SMSC to send the message through, or NULL for the
-     *  defatult SMSC
+     *  default SMSC
      * @param pdu the raw PDU to send
      * @param sentIntent if not NULL this <code>Intent</code> is
      *  broadcast when the message is successfully sent, or failed.
@@ -758,7 +758,7 @@
      *  <code>RESULT_ERROR_RADIO_OFF</code>
      *  <code>RESULT_ERROR_NULL_PDU</code>.
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntent if not NULL this <code>Intent</code> is
      *  broadcast when the message is delivered to the recipient.  The
@@ -965,10 +965,10 @@
     private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {
+            if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
                 mStorageAvailable = false;
                 mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
-            } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
+            } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
                 mStorageAvailable = true;
                 mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
             } else {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 4508e9a..f48c956 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -100,7 +100,6 @@
     CdmaCallTracker mCT;
     CdmaSMSDispatcher mSMS;
     CdmaServiceStateTracker mSST;
-    RuimFileHandler mRuimFileHandler;
     RuimRecords mRuimRecords;
     RuimCard mRuimCard;
     ArrayList <CdmaMmiCode> mPendingMmis = new ArrayList<CdmaMmiCode>();
@@ -158,7 +157,7 @@
         mDataConnection = new CdmaDataConnectionTracker (this);
         mRuimCard = new RuimCard(this);
         mRuimPhoneBookInterfaceManager = new RuimPhoneBookInterfaceManager(this);
-        mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this);
+        mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this, mSMS);
         mSubInfo = new PhoneSubInfo(this);
         mEriManager = new EriManager(this, context, EriManager.ERI_FROM_XML);
 
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index cfcfd98..e97549d 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -27,6 +27,7 @@
 import com.android.internal.telephony.IccSmsInterfaceManager;
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsRawData;
 
 import java.util.ArrayList;
@@ -81,9 +82,9 @@
         }
     };
 
-    public RuimSmsInterfaceManager(CDMAPhone phone) {
+    public RuimSmsInterfaceManager(CDMAPhone phone, SMSDispatcher dispatcher) {
         super(phone);
-        mDispatcher = new CdmaSMSDispatcher(phone);
+        mDispatcher = dispatcher;
     }
 
     public void dispose() {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index e5ca519..689a972 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -117,10 +117,6 @@
     Thread debugPortThread;
     ServerSocket debugSocket;
 
-    private int mReportedRadioResets;
-    private int mReportedAttemptedConnects;
-    private int mReportedSuccessfulConnects;
-
     private String mImei;
     private String mImeiSv;
     private String mVmNumber;
@@ -151,7 +147,7 @@
         mSimCard = new SimCard(this);
         if (!unitTestMode) {
             mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
-            mSimSmsIntManager = new SimSmsInterfaceManager(this);
+            mSimSmsIntManager = new SimSmsInterfaceManager(this, mSMS);
             mSubInfo = new PhoneSubInfo(this);
         }
         mStkService = StkService.getInstance(mCM, mSIMRecords, mContext,
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index 2028ca4..67ecc77 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -25,6 +25,7 @@
 import com.android.internal.telephony.IccConstants;
 import com.android.internal.telephony.IccSmsInterfaceManager;
 import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsRawData;
 
 import java.util.ArrayList;
@@ -78,9 +79,9 @@
         }
     };
 
-    public SimSmsInterfaceManager(GSMPhone phone) {
+    public SimSmsInterfaceManager(GSMPhone phone, SMSDispatcher dispatcher) {
         super(phone);
-        mDispatcher = new GsmSMSDispatcher(phone);
+        mDispatcher = dispatcher;
     }
 
     public void dispose() {
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
index 4b7e991..9098e6f 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -176,8 +176,7 @@
     }
 
     public int getPhoneType() {
-        // FIXME: add SIP phone type
-        return Phone.PHONE_TYPE_GSM;
+        return Phone.PHONE_TYPE_SIP;
     }
 
     public SignalStrength getSignalStrength() {