Merge "Only enable fingerprint auth after first regular auth" into mnc-dev
diff --git a/api/current.txt b/api/current.txt
index e3bb0b8..ff66cf6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9828,6 +9828,7 @@
   public final class Resources.Theme {
     method public void applyStyle(int, boolean);
     method public void dump(int, java.lang.String, java.lang.String);
+    method public int getChangingConfigurations();
     method public android.graphics.drawable.Drawable getDrawable(int) throws android.content.res.Resources.NotFoundException;
     method public android.content.res.Resources getResources();
     method public android.content.res.TypedArray obtainStyledAttributes(int[]);
@@ -15607,6 +15608,16 @@
     field public static final int HEVCMainTierLevel62 = 16777216; // 0x1000000
     field public static final int HEVCProfileMain = 1; // 0x1
     field public static final int HEVCProfileMain10 = 2; // 0x2
+    field public static final int MPEG2LevelH14 = 2; // 0x2
+    field public static final int MPEG2LevelHL = 3; // 0x3
+    field public static final int MPEG2LevelLL = 0; // 0x0
+    field public static final int MPEG2LevelML = 1; // 0x1
+    field public static final int MPEG2Profile422 = 2; // 0x2
+    field public static final int MPEG2ProfileHigh = 5; // 0x5
+    field public static final int MPEG2ProfileMain = 1; // 0x1
+    field public static final int MPEG2ProfileSNR = 3; // 0x3
+    field public static final int MPEG2ProfileSimple = 0; // 0x0
+    field public static final int MPEG2ProfileSpatial = 4; // 0x4
     field public static final int MPEG4Level0 = 1; // 0x1
     field public static final int MPEG4Level0b = 2; // 0x2
     field public static final int MPEG4Level1 = 4; // 0x4
diff --git a/api/system-current.txt b/api/system-current.txt
index 535bfc7..ffcb86f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -10122,6 +10122,7 @@
   public final class Resources.Theme {
     method public void applyStyle(int, boolean);
     method public void dump(int, java.lang.String, java.lang.String);
+    method public int getChangingConfigurations();
     method public android.graphics.drawable.Drawable getDrawable(int) throws android.content.res.Resources.NotFoundException;
     method public android.content.res.Resources getResources();
     method public android.content.res.TypedArray obtainStyledAttributes(int[]);
@@ -16829,6 +16830,16 @@
     field public static final int HEVCMainTierLevel62 = 16777216; // 0x1000000
     field public static final int HEVCProfileMain = 1; // 0x1
     field public static final int HEVCProfileMain10 = 2; // 0x2
+    field public static final int MPEG2LevelH14 = 2; // 0x2
+    field public static final int MPEG2LevelHL = 3; // 0x3
+    field public static final int MPEG2LevelLL = 0; // 0x0
+    field public static final int MPEG2LevelML = 1; // 0x1
+    field public static final int MPEG2Profile422 = 2; // 0x2
+    field public static final int MPEG2ProfileHigh = 5; // 0x5
+    field public static final int MPEG2ProfileMain = 1; // 0x1
+    field public static final int MPEG2ProfileSNR = 3; // 0x3
+    field public static final int MPEG2ProfileSimple = 0; // 0x0
+    field public static final int MPEG2ProfileSpatial = 4; // 0x4
     field public static final int MPEG4Level0 = 1; // 0x1
     field public static final int MPEG4Level0b = 2; // 0x2
     field public static final int MPEG4Level1 = 4; // 0x4
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index aea413d..470804d 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -231,7 +231,7 @@
      * device owner app.
      *
      * <p>The broadcast will be limited to the {@link DeviceAdminReceiver} component specified in
-     * the (@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME) field
+     * the {@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME} field
      * of the original intent or NFC bump that started the provisioning process. You will generally
      * handle this in {@link DeviceAdminReceiver#onReadyForUserInitialization}.
      *
@@ -450,9 +450,9 @@
      *
      * <p>It is not assumed that the device initializer is finished when it returns from
      * this call, as it may do additional setup asynchronously. The device initializer must call
-     * {DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any additional
-     * setup (such as adding an account by using the {@link AccountManager}) in order for the user
-     * to be functional.
+     * {@link DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any
+     * additional setup (such as adding an account by using the {@link AccountManager}) in order for
+     * the user to be functional.
      *
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9f71ea5..4b72dc3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -412,7 +412,7 @@
         = "android.app.action.MANAGED_PROFILE_PROVISIONED";
 
     /**
-     * A boolean extra indicating whether device encryption is required as part of Device Owner
+     * A boolean extra indicating whether device encryption can be skipped as part of Device Owner
      * provisioning.
      *
      * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 16f6b1e..43cc63b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -570,13 +570,16 @@
         Configuration.NATIVE_CONFIG_DENSITY,                // DENSITY
         Configuration.NATIVE_CONFIG_LAYOUTDIR,              // LAYOUT DIRECTION
     };
-    /** @hide
+
+    /**
      * Convert Java change bits to native.
+     *
+     * @hide
      */
     public static int activityInfoConfigToNative(int input) {
         int output = 0;
-        for (int i=0; i<CONFIG_NATIVE_BITS.length; i++) {
-            if ((input&(1<<i)) != 0) {
+        for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) {
+            if ((input & (1 << i)) != 0) {
                 output |= CONFIG_NATIVE_BITS[i];
             }
         }
@@ -584,6 +587,21 @@
     }
 
     /**
+     * Convert native change bits to Java.
+     *
+     * @hide
+     */
+    public static int activityInfoConfigNativeToJava(int input) {
+        int output = 0;
+        for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) {
+            if ((input & CONFIG_NATIVE_BITS[i]) != 0) {
+                output |= (1 << i);
+            }
+        }
+        return output;
+    }
+
+    /**
      * @hide
      * Unfortunately some developers (OpenFeint I am looking at you) have
      * compared the configChanges bit field against absolute values, so if we
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index a176593..525059f 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -789,6 +789,7 @@
                                                                 TypedValue outValue,
                                                                 boolean resolve);
     /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix);
+    /*package*/ native static final int getThemeChangingConfigurations(long theme);
 
     private native final long openXmlAssetNative(int cookie, String fileName);
 
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 6e77e33..ae41b69 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1731,6 +1731,19 @@
         }
 
         /**
+         * Returns a bit mask of configuration changes that will impact this
+         * theme (and thus require completely reloading it).
+         *
+         * @return a bit mask of configuration changes, as defined by
+         *         {@link ActivityInfo}
+         * @see ActivityInfo
+         */
+        public int getChangingConfigurations() {
+            final int nativeChangingConfig = AssetManager.getThemeChangingConfigurations(mTheme);
+            return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
+        }
+
+        /**
          * Print contents of this theme out to the log.  For debugging only.
          * 
          * @param priority The log priority to use.
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 7bebbfb..a55a08c 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -278,7 +278,7 @@
                 if (style[j] instanceof TypefaceSpan) {
                     String s = ((TypefaceSpan) style[j]).getFamily();
 
-                    if (s.equals("monospace")) {
+                    if ("monospace".equals(s)) {
                         out.append("<tt>");
                     }
                 }
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index 240d9df..d77b998 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -20,6 +20,8 @@
 import android.os.SystemClock;
 import android.util.EventLog;
 
+import dalvik.system.VMRuntime;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
@@ -96,7 +98,7 @@
     
     public static void forceGc(String reason) {
         EventLog.writeEvent(2741, reason);
-        Runtime.getRuntime().gc();
+        VMRuntime.getRuntime().requestConcurrentGC();
     }
     
     static void forceBinderGc() {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index daf5a61..db495dd 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -999,6 +999,13 @@
     return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
 }
 
+static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz,
+                                                                        jlong themeHandle)
+{
+    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
+    return theme->getChangingConfigurations();
+}
+
 static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz,
                                                    jlong themeHandle, jint pri,
                                                    jstring tag, jstring prefix)
@@ -2103,6 +2110,8 @@
         (void*) android_content_AssetManager_copyTheme },
     { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
         (void*) android_content_AssetManager_loadThemeAttributeValue },
+    { "getThemeChangingConfigurations", "(J)I",
+        (void*) android_content_AssetManager_getThemeChangingConfigurations },
     { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
         (void*) android_content_AssetManager_dumpTheme },
     { "applyStyle","(JIIJ[I[I[I)Z",
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index df278c8..587e7fa 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1662,6 +1662,12 @@
                 uint32_t* inoutTypeSpecFlags = NULL,
                 ResTable_config* inoutConfig = NULL) const;
 
+        /**
+         * Returns a bit mask of configuration changes that will impact this
+         * theme (and thus require completely reloading it).
+         */
+        uint32_t getChangingConfigurations() const;
+
         void dumpToLog() const;
         
     private:
@@ -1688,6 +1694,7 @@
 
         const ResTable& mTable;
         package_info*   mPackages[Res_MAXPACKAGE];
+        uint32_t        mTypeSpecFlags;
     };
 
     void setParameters(const ResTable_config* params);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 04ebe70..19a5beb 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -3147,6 +3147,7 @@
 
 ResTable::Theme::Theme(const ResTable& table)
     : mTable(table)
+    , mTypeSpecFlags(0)
 {
     memset(mPackages, 0, sizeof(mPackages));
 }
@@ -3205,6 +3206,8 @@
         return N;
     }
 
+    mTypeSpecFlags |= bagTypeSpecFlags;
+
     uint32_t curPackage = 0xffffffff;
     ssize_t curPackageIndex = 0;
     package_info* curPI = NULL;
@@ -3323,6 +3326,8 @@
         }
     }
 
+    mTypeSpecFlags = other.mTypeSpecFlags;
+
     if (kDebugTableTheme) {
         ALOGI("Final theme:");
         dumpToLog();
@@ -3417,6 +3422,11 @@
             inoutTypeSpecFlags, inoutConfig);
 }
 
+uint32_t ResTable::Theme::getChangingConfigurations() const
+{
+    return mTypeSpecFlags;
+}
+
 void ResTable::Theme::dumpToLog() const
 {
     ALOGI("Theme %p:\n", this);
diff --git a/libs/hwui/unit_tests/Android.mk b/libs/hwui/unit_tests/Android.mk
index 51601b0..917e646 100644
--- a/libs/hwui/unit_tests/Android.mk
+++ b/libs/hwui/unit_tests/Android.mk
@@ -27,6 +27,7 @@
 
 LOCAL_SRC_FILES += \
     unit_tests/ClipAreaTests.cpp \
+    unit_tests/DamageAccumulatorTests.cpp \
     unit_tests/LinearAllocatorTests.cpp \
     unit_tests/main.cpp
 
diff --git a/libs/hwui/unit_tests/DamageAccumulatorTests.cpp b/libs/hwui/unit_tests/DamageAccumulatorTests.cpp
new file mode 100644
index 0000000..c8d6004
--- /dev/null
+++ b/libs/hwui/unit_tests/DamageAccumulatorTests.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <DamageAccumulator.h>
+#include <Matrix.h>
+#include <utils/LinearAllocator.h>
+
+#include <SkRect.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+// Test that push & pop are propegating the dirty rect
+// There is no transformation of the dirty rect, the input is the same
+// as the output.
+TEST(DamageAccumulator, identity) {
+    DamageAccumulator da;
+    Matrix4 identity;
+    SkRect curDirty;
+    identity.loadIdentity();
+    da.pushTransform(&identity);
+    da.dirty(50, 50, 100, 100);
+    da.pushTransform(&identity);
+    da.peekAtDirty(&curDirty);
+    ASSERT_EQ(SkRect(), curDirty);
+    da.popTransform();
+    da.peekAtDirty(&curDirty);
+    ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
+    da.popTransform();
+    da.finish(&curDirty);
+    ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
+}
+
+// Test that transformation is happening at the correct levels via
+// peekAtDirty & popTransform. Just uses a simple translate to test this
+TEST(DamageAccumulator, translate) {
+    DamageAccumulator da;
+    Matrix4 translate;
+    SkRect curDirty;
+    translate.loadTranslate(25, 25, 0);
+    da.pushTransform(&translate);
+    da.dirty(50, 50, 100, 100);
+    da.peekAtDirty(&curDirty);
+    ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
+    da.popTransform();
+    da.finish(&curDirty);
+    ASSERT_EQ(SkRect::MakeLTRB(75, 75, 125, 125), curDirty);
+}
+
+// Test that dirty rectangles are being unioned across "siblings
+TEST(DamageAccumulator, union) {
+    DamageAccumulator da;
+    Matrix4 identity;
+    SkRect curDirty;
+    identity.loadIdentity();
+    da.pushTransform(&identity);
+    da.pushTransform(&identity);
+    da.dirty(50, 50, 100, 100);
+    da.popTransform();
+    da.pushTransform(&identity);
+    da.dirty(150, 50, 200, 125);
+    da.popTransform();
+    da.popTransform();
+    da.finish(&curDirty);
+    ASSERT_EQ(SkRect::MakeLTRB(50, 50, 200, 125), curDirty);
+}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 974c9af..89d419a 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1733,6 +1733,72 @@
                         maxBlocks, maxBlocksPerSecond,
                         16 /* blockWidth */, 16 /* blockHeight */,
                         1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
+                int maxWidth = 11, maxHeight = 9, maxRate = 15;
+                maxBlocks = 99;
+                maxBlocksPerSecond = 1485;
+                maxBps = 64000;
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+                    boolean supported = true;
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.MPEG2ProfileSimple:
+                            switch (profileLevel.level) {
+                                case CodecProfileLevel.MPEG2LevelML:
+                                    FR = 30; W = 45; H =  36; MBPS =  48600; FS =  1620; BR =  15000; break;
+                                default:
+                                    Log.w(TAG, "Unrecognized profile/level "
+                                            + profileLevel.profile + "/"
+                                            + profileLevel.level + " for " + mime);
+                                    errors |= ERROR_UNRECOGNIZED;
+                            }
+                            break;
+                        case CodecProfileLevel.MPEG2ProfileMain:
+                            switch (profileLevel.level) {
+                                case CodecProfileLevel.MPEG2LevelLL:
+                                    FR = 30; W = 22; H =  18; MBPS =  11880; FS =  396; BR =  4000; break;
+                                case CodecProfileLevel.MPEG2LevelML:
+                                    FR = 30; W = 45; H =  36; MBPS =  48600; FS =  1620; BR =  15000; break;
+                                case CodecProfileLevel.MPEG2LevelH14:
+                                    FR = 60; W = 90; H =  68; MBPS =  367200; FS =  6120; BR = 60000; break;
+                                case CodecProfileLevel.MPEG2LevelHL:
+                                    FR = 60; W = 120; H = 68; MBPS =  489600; FS =  8160; BR = 80000; break;
+                                default:
+                                    Log.w(TAG, "Unrecognized profile/level "
+                                            + profileLevel.profile + "/"
+                                            + profileLevel.level + " for " + mime);
+                                    errors |= ERROR_UNRECOGNIZED;
+                            }
+                            break;
+                        case CodecProfileLevel.MPEG2Profile422:
+                        case CodecProfileLevel.MPEG2ProfileSNR:
+                        case CodecProfileLevel.MPEG2ProfileSpatial:
+                        case CodecProfileLevel.MPEG2ProfileHigh:
+                            Log.i(TAG, "Unsupported profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNSUPPORTED;
+                            supported = false;
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    if (supported) {
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                    }
+                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                    maxBlocks = Math.max(FS, maxBlocks);
+                    maxBps = Math.max(BR * 1000, maxBps);
+                    maxWidth = Math.max(W, maxWidth);
+                    maxHeight = Math.max(H, maxHeight);
+                    maxRate = Math.max(FR, maxRate);
+                }
+                applyMacroBlockLimits(maxWidth, maxHeight,
+                        maxBlocks, maxBlocksPerSecond,
+                        16 /* blockWidth */, 16 /* blockHeight */,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+                mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
             } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
                 int maxWidth = 11, maxHeight = 9, maxRate = 15;
                 maxBlocks = 99;
@@ -2343,6 +2409,20 @@
         public static final int MPEG4Level4a     = 0x40;
         public static final int MPEG4Level5      = 0x80;
 
+        // from OMX_VIDEO_MPEG2PROFILETYPE
+        public static final int MPEG2ProfileSimple              = 0x00;
+        public static final int MPEG2ProfileMain                = 0x01;
+        public static final int MPEG2Profile422                 = 0x02;
+        public static final int MPEG2ProfileSNR                 = 0x03;
+        public static final int MPEG2ProfileSpatial             = 0x04;
+        public static final int MPEG2ProfileHigh                = 0x05;
+
+        // from OMX_VIDEO_MPEG2LEVELTYPE
+        public static final int MPEG2LevelLL     = 0x00;
+        public static final int MPEG2LevelML     = 0x01;
+        public static final int MPEG2LevelH14    = 0x02;
+        public static final int MPEG2LevelHL     = 0x03;
+
         // from OMX_AUDIO_AACPROFILETYPE
         public static final int AACObjectMain       = 1;
         public static final int AACObjectLC         = 2;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index 86c23c7..16b4c43 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -60,6 +60,7 @@
     public static final String VIDEO_H264_AAC = "/sdcard/media_api/video/H264_320_AAC_64.3gp";
     public static final String VIDEO_H264_AMR = "/sdcard/media_api/video/H264_320_AMRNB_6.3gp";
     public static final String VIDEO_HEVC_AAC = "/sdcard/media_api/video/HEVC_320_AAC_128.mp4";
+    public static final String VIDEO_MPEG2_AAC = "/sdcard/media_api/video/MPEG2_1500_AAC_128.mp4";
     public static final String VIDEO_HIGHRES_H263 = "/sdcard/media_api/video/H263_500_AMRNB_12.3gp";
     public static final String VIDEO_HIGHRES_MP4 = "/sdcard/media_api/video/H264_500_AAC_128.3gp";
     public static final String VIDEO_WEBM = "/sdcard/media_api/video/big-buck-bunny_trailer.webm";
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 244b07f..c5281657 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -432,7 +432,22 @@
         assertTrue("HEVC playback memory test", memoryResult);
     }
 
-    // Test case 4: Capture the memory usage after every 20 video only recorded
+    // Test case 4: Capture the memory usage after every 20 mpeg2 playback
+    @LargeTest
+    public void testMPEG2VideoPlaybackMemoryUsage() throws Exception {
+        boolean memoryResult = false;
+
+        mStartPid = getMediaserverPid();
+        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
+            mediaStressPlayback(MediaNames.VIDEO_MPEG2_AAC);
+            getMemoryWriteToLog(i);
+            writeProcmemInfo();
+        }
+        memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT);
+        assertTrue("MPEG2 playback memory test", memoryResult);
+    }
+
+    // Test case 5: Capture the memory usage after every 20 video only recorded
     @LargeTest
     public void testH263RecordVideoOnlyMemoryUsage() throws Exception {
         if (mCamcorderProfile != null) {
@@ -453,7 +468,7 @@
         }
     }
 
-    // Test case 5: Capture the memory usage after every 20 video only recorded
+    // Test case 6: Capture the memory usage after every 20 video only recorded
     @LargeTest
     public void testMpeg4RecordVideoOnlyMemoryUsage() throws Exception {
         if (mCamcorderProfile != null) {
@@ -474,7 +489,7 @@
         }
     }
 
-    // Test case 6: Capture the memory usage after every 20 video and audio
+    // Test case 7: Capture the memory usage after every 20 video and audio
     // recorded
     @LargeTest
     public void testRecordVideoAudioMemoryUsage() throws Exception {
@@ -496,7 +511,7 @@
         }
     }
 
-    // Test case 7: Capture the memory usage after every 20 audio only recorded
+    // Test case 8: Capture the memory usage after every 20 audio only recorded
     @LargeTest
     public void testRecordAudioOnlyMemoryUsage() throws Exception {
         boolean memoryResult = false;
@@ -511,7 +526,7 @@
         assertTrue("audio record only memory test", memoryResult);
     }
 
-    // Test case 8: Capture the memory usage after every 20 camera preview
+    // Test case 9: Capture the memory usage after every 20 camera preview
     @LargeTest
     public void testCameraPreviewMemoryUsage() throws Exception {
         boolean memoryResult = false;
diff --git a/media/tests/contents/media_api/video/MPEG2_1500_AAC_128.mp4 b/media/tests/contents/media_api/video/MPEG2_1500_AAC_128.mp4
new file mode 100644
index 0000000..33f66a0
--- /dev/null
+++ b/media/tests/contents/media_api/video/MPEG2_1500_AAC_128.mp4
Binary files differ
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 2e0bece..9dd2b20 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -449,7 +449,7 @@
         InputStream src = null;
         OutputStream dst = null;
 
-        boolean errorOccurred = false;
+        IOException copyError = null;
         try {
             srcFile = mSrcClient.openFile(srcUri, "r", canceller);
             dstFile = mDstClient.openFile(dstUri, "w", canceller);
@@ -462,24 +462,35 @@
                 dst.write(buffer, 0, len);
                 makeProgress(len);
             }
+
             srcFile.checkError();
-            dstFile.checkError();
         } catch (IOException e) {
-            errorOccurred = true;
-            Log.e(TAG, "Error while copying " + srcUri.toString(), e);
-            try {
-                mFailedFiles.add(DocumentInfo.fromUri(getContentResolver(), srcUri));
-            } catch (FileNotFoundException ignore) {
-                Log.w(TAG, "Source file gone: " + srcUri, e);
-              // The source file is gone.
-            }
+            copyError = e;
         } finally {
+            if (copyError != null) {
+                try {
+                    dstFile.closeWithError(copyError.getMessage());
+                } catch (IOException e) {
+                    Log.e(TAG, "Error closing destination", e);
+                }
+            }
             // This also ensures the file descriptors are closed.
             IoUtils.closeQuietly(src);
             IoUtils.closeQuietly(dst);
         }
 
-        if (errorOccurred || mIsCancelled) {
+        if (copyError != null) {
+            // Log errors.
+            Log.e(TAG, "Error while copying " + srcUri.toString(), copyError);
+            try {
+                mFailedFiles.add(DocumentInfo.fromUri(getContentResolver(), srcUri));
+            } catch (FileNotFoundException ignore) {
+                Log.w(TAG, "Source file gone: " + srcUri, copyError);
+              // The source file is gone.
+            }
+        }
+
+        if (copyError != null || mIsCancelled) {
             // Clean up half-copied files.
             canceller.cancel();
             try {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index 13f7daa..b1c84dd 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -16,23 +16,21 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.model.DocumentInfo.getCursorString;
-
-import android.app.NotificationManager;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
 import android.test.MoreAsserts;
 import android.test.ServiceTestCase;
+import android.test.mock.MockContentResolver;
 import android.util.Log;
 
 import com.android.documentsui.model.DocumentInfo;
@@ -43,40 +41,93 @@
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
-import org.mockito.Mockito;
-
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 public class CopyTest extends ServiceTestCase<CopyService> {
 
+    /**
+     * A test resolver that enables this test suite to listen for notifications that mark when copy
+     * operations are done.
+     */
+    class TestContentResolver extends MockContentResolver {
+        private CountDownLatch mReadySignal;
+        private CountDownLatch mNotificationSignal;
+
+        public TestContentResolver() {
+            mReadySignal = new CountDownLatch(1);
+        }
+
+        /**
+         * Wait for the given number of files to be copied to destination. Times out after 1 sec.
+         */
+        public void waitForChanges(int count) throws Exception {
+            // Wait for no more than 1 second by default.
+            waitForChanges(count, 1000);
+        }
+
+        /**
+         * Wait for files to be copied to destination.
+         *
+         * @param count Number of files to wait for.
+         * @param timeOut Timeout in ms. TimeoutException will be thrown if this function times out.
+         */
+        public void waitForChanges(int count, int timeOut) throws Exception {
+            mNotificationSignal = new CountDownLatch(count);
+            // Signal that the test is now waiting for files.
+            mReadySignal.countDown();
+            if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) {
+                throw new TimeoutException("Timed out waiting for files to be copied.");
+            }
+        }
+
+        @Override
+        public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+            // Wait until the test is ready to receive file notifications.
+            try {
+                mReadySignal.await();
+            } catch (InterruptedException e) {
+                Log.d(TAG, "Interrupted while waiting for file copy readiness");
+                Thread.currentThread().interrupt();
+            }
+            if (DocumentsContract.isDocumentUri(mContext, uri)) {
+                Log.d(TAG, "Notification: " + uri);
+                // Watch for document URI change notifications - this signifies the end of a copy.
+                mNotificationSignal.countDown();
+            }
+        }
+    };
+
     public CopyTest() {
         super(CopyService.class);
     }
 
-    private static String TAG = "CopyTest";
-    // This must match the authority for the StubProvider.
     private static String AUTHORITY = "com.android.documentsui.stubprovider";
+    private static String DST = "sd1";
+    private static String SRC = "sd0";
+    private static String TAG = "CopyTest";
     private List<RootInfo> mRoots;
     private Context mContext;
-    private ContentResolver mResolver;
+    private TestContentResolver mResolver;
     private ContentProviderClient mClient;
-    private NotificationManager mNotificationManager;
+    private StubProvider mStorage;
+    private Context mSystemContext;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        setupTestContext();
 
-        mResolver = mContext.getContentResolver();
+        setupTestContext();
         mClient = mResolver.acquireContentProviderClient(AUTHORITY);
 
         // Reset the stub provider's storage.
-        mClient.call("clear", "", null);
+        mStorage.clearCacheAndBuildRoots();
 
         mRoots = Lists.newArrayList();
         Uri queryUri = DocumentsContract.buildRootsUri(AUTHORITY);
@@ -84,9 +135,7 @@
         try {
             cursor = mClient.query(queryUri, null, null, null, null);
             while (cursor.moveToNext()) {
-                final RootInfo root = RootInfo.fromRootsCursor(AUTHORITY, cursor);
-                final String id = root.rootId;
-                mRoots.add(root);
+                mRoots.add(RootInfo.fromRootsCursor(AUTHORITY, cursor));
             }
         } finally {
             IoUtils.closeQuietly(cursor);
@@ -100,68 +149,94 @@
         super.tearDown();
     }
 
-    public List<Uri> setupTestFiles() throws Exception {
-        Uri rootUri = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(0).documentId);
-        List<Uri> testFiles = Lists.newArrayList(
-                DocumentsContract.createDocument(mClient, rootUri, "text/plain", "test0.txt"),
-                DocumentsContract.createDocument(mClient, rootUri, "text/plain", "test1.txt"),
-                DocumentsContract.createDocument(mClient, rootUri, "text/plain", "test2.txt")
-        );
-        String testContent[] = {
-                "The five boxing wizards jump quickly",
-                "The quick brown fox jumps over the lazy dog",
-                "Jackdaws love my big sphinx of quartz"
-        };
-        for (int i = 0; i < testFiles.size(); ++i) {
-            ParcelFileDescriptor pfd = null;
-            OutputStream out = null;
-            try {
-                pfd = mClient.openFile(testFiles.get(i), "w");
-                out = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
-                out.write(testContent[i].getBytes());
-            } finally {
-                IoUtils.closeQuietly(out);
-            }
-        }
-        return testFiles;
-    }
-
     /**
      * Test copying a single file.
      */
     public void testCopyFile() throws Exception {
-        Uri testFile = setupTestFiles().get(0);
+        String srcPath = "/test0.txt";
+        Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
+                "The five boxing wizards jump quickly".getBytes());
 
-        // Just copy one file.
+        assertDstFileCountEquals(0);
+
         copyToDestination(Lists.newArrayList(testFile));
 
-        // A call to NotificationManager.cancel marks the end of the copy operation.
-        Mockito.verify(mNotificationManager, Mockito.timeout(1000)).cancel(Mockito.anyString(),
-                Mockito.anyInt());
+        // 2 operations: file creation, then writing data.
+        mResolver.waitForChanges(2);
 
         // Verify that one file was copied; check file contents.
         assertDstFileCountEquals(1);
-        assertCopied(testFile);
+        assertCopied(srcPath);
     }
 
     /**
      * Test copying multiple files.
      */
     public void testCopyMultipleFiles() throws Exception {
-        List<Uri> testFiles = setupTestFiles();
+        String testContent[] = {
+                "The five boxing wizards jump quickly",
+                "The quick brown fox jumps over the lazy dog",
+                "Jackdaws love my big sphinx of quartz"
+        };
+        String srcPaths[] = {
+                "/test0.txt",
+                "/test1.txt",
+                "/test2.txt"
+        };
+        List<Uri> testFiles = Lists.newArrayList(
+                mStorage.createFile(SRC, srcPaths[0], "text/plain", testContent[0].getBytes()),
+                mStorage.createFile(SRC, srcPaths[1], "text/plain", testContent[1].getBytes()),
+                mStorage.createFile(SRC, srcPaths[2], "text/plain", testContent[2].getBytes()));
+
+        assertDstFileCountEquals(0);
+
         // Copy all the test files.
         copyToDestination(testFiles);
 
-        // A call to NotificationManager.cancel marks the end of the copy operation.
-        Mockito.verify(mNotificationManager, Mockito.timeout(1000)).cancel(Mockito.anyString(),
-                Mockito.anyInt());
+        // 3 file creations, 3 file writes.
+        mResolver.waitForChanges(6);
 
         assertDstFileCountEquals(3);
-        for (Uri testFile : testFiles) {
-            assertCopied(testFile);
+        for (String path : srcPaths) {
+            assertCopied(path);
         }
     }
 
+    public void testCopyEmptyDir() throws Exception {
+        String srcPath = "/emptyDir";
+        Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
+                null);
+
+        assertDstFileCountEquals(0);
+
+        copyToDestination(Lists.newArrayList(testDir));
+
+        // Just 1 operation: Directory creation.
+        mResolver.waitForChanges(1);
+
+        assertDstFileCountEquals(1);
+
+        File dst = mStorage.getFile(DST, srcPath);
+        assertTrue(dst.isDirectory());
+    }
+
+    public void testReadErrors() throws Exception {
+        String srcPath = "/test0.txt";
+        Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
+                "The five boxing wizards jump quickly".getBytes());
+
+        assertDstFileCountEquals(0);
+
+        mStorage.simulateReadErrors(true);
+
+        copyToDestination(Lists.newArrayList(testFile));
+
+        // 3 operations: file creation, writing, then deletion (due to failed copy).
+        mResolver.waitForChanges(3);
+
+        assertDstFileCountEquals(0);
+    }
+
     /**
      * Copies the given files to a pre-determined destination.
      *
@@ -200,82 +275,55 @@
         assertEquals("Incorrect file count after copy", expected, count);
     }
 
-    /**
-     * Verifies that the file pointed to by the given URI was correctly copied to the destination.
-     */
-    private void assertCopied(Uri src) throws Exception {
-        Cursor cursor = null;
-        String srcName = null;
+    private void assertCopied(String path) throws Exception {
+        File srcFile = mStorage.getFile(SRC, path);
+        File dstFile = mStorage.getFile(DST, path);
+        assertNotNull(dstFile);
+
+        FileInputStream src = null;
+        FileInputStream dst = null;
         try {
-            cursor = mClient.query(src, null, null, null, null);
-            if (cursor.moveToFirst()) {
-                srcName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
-            }
+            src = new FileInputStream(srcFile);
+            dst = new FileInputStream(dstFile);
+            byte[] srcbuf = Streams.readFully(src);
+            byte[] dstbuf = Streams.readFully(dst);
+
+            MoreAsserts.assertEquals(srcbuf, dstbuf);
         } finally {
-            IoUtils.closeQuietly(cursor);
+            IoUtils.closeQuietly(src);
+            IoUtils.closeQuietly(dst);
         }
-        Uri dst = getDstFileUri(srcName);
-
-        InputStream in0 = null;
-        InputStream in1 = null;
-        try {
-            in0 = new ParcelFileDescriptor.AutoCloseInputStream(mClient.openFile(src, "r"));
-            in1 = new ParcelFileDescriptor.AutoCloseInputStream(mClient.openFile(dst, "r"));
-
-            byte[] buffer0 = Streams.readFully(in0);
-            byte[] buffer1 = Streams.readFully(in1);
-
-            MoreAsserts.assertEquals(buffer0, buffer1);
-        } finally {
-            IoUtils.closeQuietly(in0);
-            IoUtils.closeQuietly(in1);
-        }
-    }
-
-    /**
-     * Generates a file URI from a given filename. This assumes the file already exists in the
-     * destination root.
-     */
-    private Uri getDstFileUri(String filename) throws RemoteException {
-        final Uri dstFileQuery = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
-                mRoots.get(1).documentId);
-        Cursor cursor = null;
-        try {
-            // StubProvider doesn't seem to support query strings; filter the results manually.
-            cursor = mClient.query(dstFileQuery, null, null, null, null);
-            while (cursor.moveToNext()) {
-                if (filename.equals(getCursorString(cursor, Document.COLUMN_DISPLAY_NAME))) {
-                    return DocumentsContract.buildDocumentUri(AUTHORITY,
-                            getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
-                }
-            }
-        } finally {
-            IoUtils.closeQuietly(cursor);
-        }
-        return null;
     }
 
     /**
      * Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to
      * listen for notification events, to gauge copy progress.
+     *
+     * @throws FileNotFoundException
      */
-    private void setupTestContext() {
-        mContext = getSystemContext();
-        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
+    private void setupTestContext() throws FileNotFoundException {
+        mSystemContext = getSystemContext();
 
-        mNotificationManager = Mockito.spy((NotificationManager) mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE));
-
-        // Insert a stub NotificationManager that enables us to listen for when copying is complete.
-        setContext(new ContextWrapper(mContext) {
+        // Set up the context with the test content resolver.
+        mResolver = new TestContentResolver();
+        mContext = new ContextWrapper(mSystemContext) {
             @Override
-            public Object getSystemService(String name) {
-                if (Context.NOTIFICATION_SERVICE.equals(name)) {
-                    return mNotificationManager;
-                } else {
-                    return super.getSystemService(name);
-                }
+            public ContentResolver getContentResolver() {
+                return mResolver;
             }
-        });
+        };
+        setContext(mContext);
+
+        // Create a local stub provider and add it to the content resolver.
+        ProviderInfo info = new ProviderInfo();
+        info.authority = AUTHORITY;
+        info.exported = true;
+        info.grantUriPermissions = true;
+        info.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS;
+        info.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS;
+
+        mStorage = new StubProvider();
+        mStorage.attachInfo(mContext, info);
+        mResolver.addProvider(AUTHORITY, mStorage);
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 438f6cd..8cef433 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -21,9 +21,10 @@
 import android.content.pm.ProviderInfo;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
-import android.database.MatrixCursor.RowBuilder;
 import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
 import android.graphics.Point;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.FileUtils;
@@ -32,15 +33,16 @@
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsProvider;
+import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.google.android.collect.Maps;
 
 import libcore.io.IoUtils;
 
-import java.io.FileOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -70,6 +72,7 @@
     private String mAuthority;
     private SharedPreferences mPrefs;
     private Map<String, RootInfo> mRoots;
+    private boolean mSimulateReadErrors;
 
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
@@ -83,7 +86,8 @@
         return true;
     }
 
-    private void clearCacheAndBuildRoots() {
+    @VisibleForTesting
+    public void clearCacheAndBuildRoots() {
         final File cacheDir = getContext().getCacheDir();
         removeRecursively(cacheDir);
         mStorage.clear();
@@ -164,7 +168,7 @@
         } else {
             try {
                 if (!file.createNewFile()) {
-                    throw new FileNotFoundException();
+                    throw new IllegalStateException("The file " + file.getPath() + " already exists");
                 }
             } catch (IOException e) {
                 throw new FileNotFoundException();
@@ -173,6 +177,10 @@
 
         final StubDocument document = new StubDocument(file, mimeType, parentDocument);
         notifyParentChanged(document.parentId);
+        getContext().getContentResolver().notifyChange(
+                DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
+                null, false);
+
         return document.documentId;
     }
 
@@ -187,6 +195,9 @@
             document.rootInfo.size -= fileSize;
         }
         notifyParentChanged(document.parentId);
+        getContext().getContentResolver().notifyChange(
+                DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
+                null, false);
     }
 
     @Override
@@ -226,7 +237,17 @@
             throw new FileNotFoundException();
 
         if ("r".equals(mode)) {
-            return ParcelFileDescriptor.open(document.file, ParcelFileDescriptor.MODE_READ_ONLY);
+            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(document.file,
+                    ParcelFileDescriptor.MODE_READ_ONLY);
+            if (mSimulateReadErrors) {
+                pfd = new ParcelFileDescriptor(pfd) {
+                    @Override
+                    public void checkError() throws IOException {
+                        throw new IOException("Test error");
+                    }
+                };
+            }
+            return pfd;
         }
         if ("w".equals(mode)) {
             return startWrite(document);
@@ -235,6 +256,11 @@
         throw new FileNotFoundException();
     }
 
+    @VisibleForTesting
+    public void simulateReadErrors(boolean b) {
+        mSimulateReadErrors = b;
+    }
+
     @Override
     public AssetFileDescriptor openDocumentThumbnail(
             String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
@@ -281,11 +307,15 @@
                         }
                     }
                 } catch (IOException e) {
+                    Log.e(TAG, "Error on close", e);
                     closePipeWithErrorSilently(readPipe, e.getMessage());
                 } finally {
                     IoUtils.closeQuietly(inputStream);
                     IoUtils.closeQuietly(outputStream);
                     notifyParentChanged(document.parentId);
+                    getContext().getContentResolver().notifyChange(
+                            DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
+                            null, false);
                 }
             }
         }.start();
@@ -302,7 +332,6 @@
 
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
-        Log.d(TAG, "call: " + method + arg);
         switch (method) {
             case "clear":
                 clearCacheAndBuildRoots();
@@ -376,30 +405,51 @@
         }
     }
 
-    public File createFile(String rootId, File parent, String mimeType, String name)
-            throws IOException {
-        StubDocument parentDoc = null;
+    @VisibleForTesting
+    public Uri createFile(String rootId, String path, String mimeType, byte[] content)
+            throws FileNotFoundException, IOException {
+        StubDocument root = mRoots.get(rootId).rootDocument;
+        if (root == null) {
+            throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
+        }
+        File file = new File(root.file, path.substring(1));
+        StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
         if (parent == null) {
-            // Use the root dir as the parent, if one wasn't specified.
-            parentDoc = mRoots.get(rootId).rootDocument;
-        } else {
-            // Verify that the parent exists and is a directory.
-            parentDoc = mStorage.get(getDocumentIdForFile(parent));
-            if (parentDoc == null) {
-                throw new IllegalArgumentException("Parent file not found.");
-            }
-            if (!Document.MIME_TYPE_DIR.equals(parentDoc.mimeType)) {
-                throw new IllegalArgumentException("Parent file must be a directory.");
-            }
+            parent = mStorage.get(createFile(rootId, file.getParentFile().getPath(),
+                    DocumentsContract.Document.MIME_TYPE_DIR, null));
         }
-        File file = new File(parentDoc.file, name);
-        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
-            file.mkdir();
+
+        if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
+            if (!file.mkdirs()) {
+                throw new FileNotFoundException("Couldn't create directory " + file.getPath());
+            }
         } else {
-            file.createNewFile();
+            if (!file.createNewFile()) {
+                throw new FileNotFoundException("Couldn't create file " + file.getPath());
+            }
+            // Add content to the file.
+            FileOutputStream fout = new FileOutputStream(file);
+            fout.write(content);
+            fout.close();
         }
-        new StubDocument(file, mimeType, parentDoc);
-        return file;
+        final StubDocument document = new StubDocument(file, mimeType, parent);
+        return DocumentsContract.buildDocumentUri(mAuthority,  document.documentId);
+    }
+
+    @VisibleForTesting
+    public File getFile(String rootId, String path) throws FileNotFoundException {
+        StubDocument root = mRoots.get(rootId).rootDocument;
+        if (root == null) {
+            throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
+        }
+        // Convert the path string into a path that's relative to the root.
+        File needle = new File(root.file, path.substring(1));
+
+        StubDocument found = mStorage.get(getDocumentIdForFile(needle));
+        if (found == null) {
+            return null;
+        }
+        return found.file;
     }
 
     final class RootInfo {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b2c9cd0..4082bf5 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -111,8 +111,10 @@
     <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
-    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the map profile. -->
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
     <string name="bluetooth_profile_map">Message Access</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the SAP profile (sharing SIM card). -->
+    <string name="bluetooth_profile_sap">SIM Access</string>
 
     <!-- Bluetooth settings.  Connection options screen.  The summary for the A2DP checkbox preference when A2DP is connected. -->
     <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
@@ -122,6 +124,8 @@
     <string name="bluetooth_opp_profile_summary_connected">Connected to file transfer server</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the map checkbox preference when map is connected. -->
     <string name="bluetooth_map_profile_summary_connected">Connected to map</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the sap checkbox preference when SAP is connected. -->
+    <string name="bluetooth_sap_profile_summary_connected">Connected to SAP</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the OPP checkbox preference when OPP is not connected. -->
     <string name="bluetooth_opp_profile_summary_not_connected">Not connected to file transfer server</string>
     <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. -->
@@ -137,6 +141,8 @@
     <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string>
     <!-- Bluetooth settings. Connection options screen.  The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. -->
     <string name="bluetooth_map_profile_summary_use_for">Use for map</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the sap checkbox preference that describes how checking it will set the sap profile as preferred. -->
+    <string name="bluetooth_sap_profile_summary_use_for">Use for SIM access</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the A2DP checkbox preference that describes how checking it will set the A2DP profile as preferred. -->
     <string name="bluetooth_a2dp_profile_summary_use_for">Use for media audio</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the headset checkbox preference that describes how checking it will set the headset profile as preferred. -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index dd2368f..7534b8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -67,10 +67,6 @@
 
     private boolean mVisible;
 
-    private int mPhonebookPermissionChoice;
-
-    private int mMessagePermissionChoice;
-
     private int mMessageRejectionCount;
 
     private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
@@ -556,6 +552,7 @@
             mConnectAfterPairing = false;  // cancel auto-connect
             setPhonebookPermissionChoice(ACCESS_UNKNOWN);
             setMessagePermissionChoice(ACCESS_UNKNOWN);
+            setSimPermissionChoice(ACCESS_UNKNOWN);
             mMessageRejectionCount = 0;
             saveMessageRejectionCount();
         }
@@ -732,6 +729,26 @@
         mDevice.setMessageAccessPermission(permission);
     }
 
+    public int getSimPermissionChoice() {
+        int permission = mDevice.getSimAccessPermission();
+        if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+            return ACCESS_ALLOWED;
+        } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+            return ACCESS_REJECTED;
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    void setSimPermissionChoice(int permissionChoice) {
+        int permission = BluetoothDevice.ACCESS_UNKNOWN;
+        if (permissionChoice == ACCESS_ALLOWED) {
+            permission = BluetoothDevice.ACCESS_ALLOWED;
+        } else if (permissionChoice == ACCESS_REJECTED) {
+            permission = BluetoothDevice.ACCESS_REJECTED;
+        }
+        mDevice.setSimAccessPermission(permission);
+    }
+
     // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
     // app's shared preferences).
     private void migrateMessagePermissionChoice() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
new file mode 100644
index 0000000..25c53e6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2015 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SapProfile handles Bluetooth SAP profile.
+ */
+final class SapProfile implements LocalBluetoothProfile {
+    private static final String TAG = "SapProfile";
+    private static boolean V = true;
+
+    private BluetoothSap mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    static final ParcelUuid[] UUIDS = {
+        BluetoothUuid.SAP,
+    };
+
+    static final String NAME = "SAP";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 10;
+
+    // These callbacks run on the main thread.
+    private final class SapServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothSap) proxy;
+            // We just bound to the service, so refresh the UI for any connected SAP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "SapProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(SapProfile.this,
+                        BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mProfileManager.callServiceConnectedListeners();
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mProfileManager.callServiceDisconnectedListeners();
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    SapProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new SapServiceListener(),
+                BluetoothProfile.SAP);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
+            if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+            return mService.disconnect(device);
+        } else {
+            return false;
+        }
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+
+        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+                ? mService.getConnectionState(device)
+                : BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_sap;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_sap_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_sap_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_cellphone;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.SAP,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up SAP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
index 1cf7248..c81f22a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
@@ -32,7 +32,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageVolume;
@@ -349,6 +348,11 @@
         final Message finished = mMeasurementHandler.obtainMessage(MeasurementHandler.MSG_COMPLETED,
                 details);
 
+        if (mVolume == null || !mVolume.isMountedReadable()) {
+            finished.sendToTarget();
+            return;
+        }
+
         if (mSharedVolume != null && mSharedVolume.isMountedReadable()) {
             final File basePath = mSharedVolume.getPathForUser(currentUser);
 
@@ -375,8 +379,10 @@
         }
 
         final File file = mVolume.getPath();
-        details.totalSize = file.getTotalSpace();
-        details.availSize = file.getFreeSpace();
+        if (file != null) {
+            details.totalSize = file.getTotalSpace();
+            details.availSize = file.getFreeSpace();
+        }
 
         // Measure all apps hosted on this volume for all users
         if (mVolume.getType() == VolumeInfo.TYPE_PRIVATE) {
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 80fecac..b836af6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -951,6 +951,12 @@
     <!-- Button label for ending zen mode in the volume dialog -->
     <string name="volume_zen_end_now">End now</string>
 
+    <!-- Content description for accessibility (not shown on the screen): volume dialog expand button. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_volume_expand">Expand</string>
+
+    <!-- Content description for accessibility (not shown on the screen): volume dialog collapse button. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_volume_collapse">Collapse</string>
+
     <!-- Screen pinning dialog title. -->
     <string name="screen_pinning_title">Screen is pinned</string>
     <!-- Screen pinning dialog description. -->
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 9434036..7115897 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.volume;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
@@ -32,6 +35,7 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.AudioSystem;
+import android.os.Debug;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -43,6 +47,8 @@
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnClickListener;
 import android.view.View.OnLayoutChangeListener;
 import android.view.View.OnTouchListener;
@@ -50,6 +56,9 @@
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
@@ -97,6 +106,7 @@
     private final ZenFooter mZenFooter;
     private final LayoutTransition mLayoutTransition;
     private final Object mSafetyWarningLock = new Object();
+    private final Accessibility mAccessibility = new Accessibility();
 
     private boolean mShowing;
     private boolean mExpanded;
@@ -174,6 +184,8 @@
         mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
         mZenFooter.init(zenModeController);
 
+        mAccessibility.init();
+
         controller.addCallback(mControllerCallbackH, mHandler);
         controller.getState();
     }
@@ -409,10 +421,13 @@
 
     protected void rescheduleTimeoutH() {
         mHandler.removeMessages(H.DISMISS);
-        final int timeout = computeTimeoutH();
-        if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout);
-        mHandler.sendMessageDelayed(mHandler
-                .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+        int timeout = -1;
+        if (!mAccessibility.mFeedbackEnabled) {
+            timeout = computeTimeoutH();
+            mHandler.sendMessageDelayed(mHandler
+                    .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+        }
+        if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
         mController.userActivity();
     }
 
@@ -475,6 +490,8 @@
         if (res == mExpandButtonRes) return;
         mExpandButtonRes = res;
         mExpandButton.setImageResource(res);
+        mExpandButton.setContentDescription(mContext.getString(mExpanded ?
+                R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand));
     }
 
     private boolean isVisibleH(VolumeRow row, boolean isActive) {
@@ -632,6 +649,7 @@
                 : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
                         ? Events.ICON_STATE_UNMUTE
                 : Events.ICON_STATE_UNKNOWN;
+        row.icon.setContentDescription(ss.name);
 
         // update slider
         updateVolumeRowSliderH(row, zenMuted);
@@ -931,6 +949,56 @@
         }
     }
 
+    private final class Accessibility {
+        private AccessibilityManager mMgr;
+        private boolean mFeedbackEnabled;
+
+        public void init() {
+            mMgr = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+            mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    // noop
+                }
+
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    updateFeedbackEnabled();
+                }
+            });
+            mDialogView.setAccessibilityDelegate(new AccessibilityDelegate() {
+                @Override
+                public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+                        AccessibilityEvent event) {
+                    rescheduleTimeoutH();
+                    return super.onRequestSendAccessibilityEvent(host, child, event);
+                }
+            });
+            mMgr.addAccessibilityStateChangeListener(new AccessibilityStateChangeListener() {
+                @Override
+                public void onAccessibilityStateChanged(boolean enabled) {
+                    updateFeedbackEnabled();
+                }
+            });
+            updateFeedbackEnabled();
+        }
+
+        private void updateFeedbackEnabled() {
+            mFeedbackEnabled = computeFeedbackEnabled();
+        }
+
+        private boolean computeFeedbackEnabled() {
+            final List<AccessibilityServiceInfo> services =
+                    mMgr.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
+            for (AccessibilityServiceInfo asi : services) {
+                if ((asi.feedbackType & FEEDBACK_ALL_MASK) != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     private static class VolumeRow {
         private View view;
         private View space;
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index e7f210b..4f10699 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -302,8 +302,12 @@
         long[] fieldIDs, long[] values, int[] sizes, long[] depClosures,
         long[] depFieldIDs) {
       validate();
-      return rsnClosureCreate(mContext, kernelID, returnValue, fieldIDs, values,
+      long c = rsnClosureCreate(mContext, kernelID, returnValue, fieldIDs, values,
           sizes, depClosures, depFieldIDs);
+      if (c == 0) {
+          throw new RSRuntimeException("Failed creating closure.");
+      }
+      return c;
     }
 
     native long rsnInvokeClosureCreate(long con, long invokeID, byte[] params,
@@ -311,8 +315,12 @@
     synchronized long nInvokeClosureCreate(long invokeID, byte[] params,
         long[] fieldIDs, long[] values, int[] sizes) {
       validate();
-      return rsnInvokeClosureCreate(mContext, invokeID, params, fieldIDs,
+      long c = rsnInvokeClosureCreate(mContext, invokeID, params, fieldIDs,
           values, sizes);
+      if (c == 0) {
+          throw new RSRuntimeException("Failed creating closure.");
+      }
+      return c;
     }
 
     native void rsnClosureSetArg(long con, long closureID, int index,
@@ -337,7 +345,11 @@
     synchronized long nScriptGroup2Create(String name, String cachePath,
                                           long[] closures) {
       validate();
-      return rsnScriptGroup2Create(mContext, name, cachePath, closures);
+      long g = rsnScriptGroup2Create(mContext, name, cachePath, closures);
+      if (g == 0) {
+          throw new RSRuntimeException("Failed creating script group.");
+      }
+      return g;
     }
 
     native void rsnScriptGroup2Execute(long con, long groupID);
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 5ef807d..80d6515 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -43,6 +43,9 @@
 
 //#define LOG_API ALOGE
 static constexpr bool kLogApi = false;
+static constexpr size_t kMaxNumberArgsAndBindings = 1000;
+static constexpr size_t kMaxNumberClosuresInScriptGroup = 1000000;
+static constexpr size_t kMaxNumberKernelArguments = 256;
 
 using namespace android;
 
@@ -328,79 +331,167 @@
                jlong returnValue, jlongArray fieldIDArray,
                jlongArray valueArray, jintArray sizeArray,
                jlongArray depClosureArray, jlongArray depFieldIDArray) {
+  jlong ret = 0;
+
   jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
   jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
-  RsScriptFieldID* fieldIDs =
-      (RsScriptFieldID*)alloca(sizeof(RsScriptFieldID) * fieldIDs_length);
-  for (int i = 0; i< fieldIDs_length; i++) {
-    fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
-  }
-
   jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
   jsize values_length = _env->GetArrayLength(valueArray);
-  uintptr_t* values = (uintptr_t*)alloca(sizeof(uintptr_t) * values_length);
-  for (int i = 0; i < values_length; i++) {
-    values[i] = (uintptr_t)jValues[i];
-  }
-
-  jint* sizes = _env->GetIntArrayElements(sizeArray, nullptr);
+  jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
   jsize sizes_length = _env->GetArrayLength(sizeArray);
-
   jlong* jDepClosures =
       _env->GetLongArrayElements(depClosureArray, nullptr);
   jsize depClosures_length = _env->GetArrayLength(depClosureArray);
-  RsClosure* depClosures =
-      (RsClosure*)alloca(sizeof(RsClosure) * depClosures_length);
-  for (int i = 0; i < depClosures_length; i++) {
-    depClosures[i] = (RsClosure)jDepClosures[i];
-  }
-
   jlong* jDepFieldIDs =
       _env->GetLongArrayElements(depFieldIDArray, nullptr);
   jsize depFieldIDs_length = _env->GetArrayLength(depFieldIDArray);
-  RsScriptFieldID* depFieldIDs =
-      (RsScriptFieldID*)alloca(sizeof(RsScriptFieldID) * depFieldIDs_length);
-  for (int i = 0; i < depClosures_length; i++) {
+
+  size_t numValues, numDependencies;
+  RsScriptFieldID* fieldIDs;
+  uintptr_t* values;
+  RsClosure* depClosures;
+  RsScriptFieldID* depFieldIDs;
+
+  if (fieldIDs_length != values_length || values_length != sizes_length) {
+      ALOGE("Unmatched field IDs, values, and sizes in closure creation.");
+      goto exit;
+  }
+
+  numValues = (size_t)fieldIDs_length;
+
+  if (depClosures_length != depFieldIDs_length) {
+      ALOGE("Unmatched closures and field IDs for dependencies in closure creation.");
+      goto exit;
+  }
+
+  numDependencies = (size_t)depClosures_length;
+
+  if (numDependencies > numValues) {
+      ALOGE("Unexpected number of dependencies in closure creation");
+      goto exit;
+  }
+
+  if (numValues > kMaxNumberArgsAndBindings) {
+      ALOGE("Too many arguments or globals in closure creation");
+      goto exit;
+  }
+
+  fieldIDs = (RsScriptFieldID*)alloca(sizeof(RsScriptFieldID) * numValues);
+  if (fieldIDs == nullptr) {
+      goto exit;
+  }
+
+  for (size_t i = 0; i < numValues; i++) {
+    fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
+  }
+
+  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
+  if (values == nullptr) {
+      goto exit;
+  }
+
+  for (size_t i = 0; i < numValues; i++) {
+    values[i] = (uintptr_t)jValues[i];
+  }
+
+  depClosures = (RsClosure*)alloca(sizeof(RsClosure) * numDependencies);
+  if (depClosures == nullptr) {
+      goto exit;
+  }
+
+  for (size_t i = 0; i < numDependencies; i++) {
+    depClosures[i] = (RsClosure)jDepClosures[i];
+  }
+
+  depFieldIDs = (RsScriptFieldID*)alloca(sizeof(RsScriptFieldID) * numDependencies);
+  if (depFieldIDs == nullptr) {
+      goto exit;
+  }
+
+  for (size_t i = 0; i < numDependencies; i++) {
     depFieldIDs[i] = (RsClosure)jDepFieldIDs[i];
   }
 
-  return (jlong)(uintptr_t)rsClosureCreate(
+  ret = (jlong)(uintptr_t)rsClosureCreate(
       (RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
-      fieldIDs, (size_t)fieldIDs_length, values, (size_t)values_length,
-      (int*)sizes, (size_t)sizes_length,
-      depClosures, (size_t)depClosures_length,
-      depFieldIDs, (size_t)depFieldIDs_length);
+      fieldIDs, numValues, values, numValues,
+      (int*)jSizes, numValues,
+      depClosures, numDependencies,
+      depFieldIDs, numDependencies);
+
+exit:
+
+  _env->ReleaseLongArrayElements(depFieldIDArray, jDepFieldIDs, JNI_ABORT);
+  _env->ReleaseLongArrayElements(depClosureArray, jDepClosures, JNI_ABORT);
+  _env->ReleaseIntArrayElements (sizeArray,       jSizes,       JNI_ABORT);
+  _env->ReleaseLongArrayElements(valueArray,      jValues,      JNI_ABORT);
+  _env->ReleaseLongArrayElements(fieldIDArray,    jFieldIDs,    JNI_ABORT);
+
+  return ret;
 }
 
 static jlong
 nInvokeClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong invokeID,
                      jbyteArray paramArray, jlongArray fieldIDArray, jlongArray valueArray,
                      jintArray sizeArray) {
+  jlong ret = 0;
+
   jbyte* jParams = _env->GetByteArrayElements(paramArray, nullptr);
   jsize jParamLength = _env->GetArrayLength(paramArray);
-
   jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
   jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
-  RsScriptFieldID* fieldIDs =
-      (RsScriptFieldID*)alloca(sizeof(RsScriptFieldID) * fieldIDs_length);
-  for (int i = 0; i< fieldIDs_length; i++) {
+  jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
+  jsize values_length = _env->GetArrayLength(valueArray);
+  jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
+  jsize sizes_length = _env->GetArrayLength(sizeArray);
+
+  size_t numValues;
+  RsScriptFieldID* fieldIDs;
+  uintptr_t* values;
+
+  if (fieldIDs_length != values_length || values_length != sizes_length) {
+      ALOGE("Unmatched field IDs, values, and sizes in closure creation.");
+      goto exit;
+  }
+
+  numValues = (size_t) fieldIDs_length;
+
+  if (numValues > kMaxNumberArgsAndBindings) {
+      ALOGE("Too many arguments or globals in closure creation");
+      goto exit;
+  }
+
+  fieldIDs = (RsScriptFieldID*)alloca(sizeof(RsScriptFieldID) * numValues);
+  if (fieldIDs == nullptr) {
+      goto exit;
+  }
+
+  for (size_t i = 0; i< numValues; i++) {
     fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
   }
 
-  jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
-  jsize values_length = _env->GetArrayLength(valueArray);
-  uintptr_t* values = (uintptr_t*)alloca(sizeof(uintptr_t) * values_length);
-  for (int i = 0; i < values_length; i++) {
+  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
+  if (values == nullptr) {
+      goto exit;
+  }
+
+  for (size_t i = 0; i < numValues; i++) {
     values[i] = (uintptr_t)jValues[i];
   }
 
-  jint* sizes = _env->GetIntArrayElements(sizeArray, nullptr);
-  jsize sizes_length = _env->GetArrayLength(sizeArray);
-
-  return (jlong)(uintptr_t)rsInvokeClosureCreate(
+  ret = (jlong)(uintptr_t)rsInvokeClosureCreate(
       (RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
-      fieldIDs, (size_t)fieldIDs_length, values, (size_t)values_length,
-      (int*)sizes, (size_t)sizes_length);
+      fieldIDs, numValues, values, numValues,
+      (int*)jSizes, numValues);
+
+exit:
+
+  _env->ReleaseIntArrayElements (sizeArray,       jSizes,       JNI_ABORT);
+  _env->ReleaseLongArrayElements(valueArray,      jValues,      JNI_ABORT);
+  _env->ReleaseLongArrayElements(fieldIDArray,    jFieldIDs,    JNI_ABORT);
+  _env->ReleaseByteArrayElements(paramArray,      jParams,      JNI_ABORT);
+
+  return ret;
 }
 
 static void
@@ -420,20 +511,40 @@
 static long
 nScriptGroup2Create(JNIEnv *_env, jobject _this, jlong con, jstring name,
                     jstring cacheDir, jlongArray closureArray) {
+  jlong ret = 0;
+
   AutoJavaStringToUTF8 nameUTF(_env, name);
   AutoJavaStringToUTF8 cacheDirUTF(_env, cacheDir);
 
   jlong* jClosures = _env->GetLongArrayElements(closureArray, nullptr);
   jsize numClosures = _env->GetArrayLength(closureArray);
-  RsClosure* closures = (RsClosure*)alloca(sizeof(RsClosure) * numClosures);
+
+  RsClosure* closures;
+
+  if (numClosures > (jsize) kMaxNumberClosuresInScriptGroup) {
+    ALOGE("Too many closures in script group");
+    goto exit;
+  }
+
+  closures = (RsClosure*)alloca(sizeof(RsClosure) * numClosures);
+  if (closures == nullptr) {
+      goto exit;
+  }
+
   for (int i = 0; i < numClosures; i++) {
     closures[i] = (RsClosure)jClosures[i];
   }
 
-  return (jlong)(uintptr_t)rsScriptGroup2Create(
+  ret = (jlong)(uintptr_t)rsScriptGroup2Create(
       (RsContext)con, nameUTF.c_str(), nameUTF.length(),
       cacheDirUTF.c_str(), cacheDirUTF.length(),
       closures, numClosures);
+
+exit:
+
+  _env->ReleaseLongArrayElements(closureArray, jClosures, JNI_ABORT);
+
+  return ret;
 }
 
 static void
@@ -1756,8 +1867,14 @@
 
     if (ains != nullptr) {
         in_len = _env->GetArrayLength(ains);
-        in_ptr = _env->GetLongArrayElements(ains, nullptr);
+        if (in_len > (jint)kMaxNumberKernelArguments) {
+            ALOGE("Too many arguments in kernel launch.");
+            // TODO (b/20758983): Report back to Java and throw an exception
+            return;
+        }
 
+        // TODO (b/20760800): Check in_ptr is not null
+        in_ptr = _env->GetLongArrayElements(ains, nullptr);
         if (sizeof(RsAllocation) == sizeof(jlong)) {
             in_allocs = (RsAllocation*)in_ptr;
 
@@ -1765,6 +1882,11 @@
             // Convert from 64-bit jlong types to the native pointer type.
 
             in_allocs = (RsAllocation*)alloca(in_len * sizeof(RsAllocation));
+            if (in_allocs == nullptr) {
+                ALOGE("Failed launching kernel for lack of memory.");
+                _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+                return;
+            }
 
             for (int index = in_len; --index >= 0;) {
                 in_allocs[index] = (RsAllocation)in_ptr[index];
diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java
index b5d566a..fd0c06d 100644
--- a/telecomm/java/android/telecom/DefaultDialerManager.java
+++ b/telecomm/java/android/telecom/DefaultDialerManager.java
@@ -14,6 +14,7 @@
 
 package android.telecom;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -35,15 +36,27 @@
     private static final String TAG = "DefaultDialerManager";
 
     /**
-     * Sets the specified package name as the default dialer application. The caller of this method
-     * needs to have permission to write to secure settings.
+     * Sets the specified package name as the default dialer application for the current user.
+     * The caller of this method needs to have permission to write to secure settings and
+     * manage users on the device.
      *
      * @hide
      * */
     public static void setDefaultDialerApplication(Context context, String packageName) {
+        setDefaultDialerApplication(context, packageName, ActivityManager.getCurrentUser());
+    }
+
+    /**
+     * Sets the specified package name as the default dialer application for the specified user.
+     * The caller of this method needs to have permission to write to secure settings and
+     * manage users on the device.
+     *
+     * @hide
+     * */
+    public static void setDefaultDialerApplication(Context context, String packageName, int user) {
         // Get old package name
-        String oldPackageName = Settings.Secure.getString(context.getContentResolver(),
-                Settings.Secure.DIALER_DEFAULT_APPLICATION);
+        String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
+                Settings.Secure.DIALER_DEFAULT_APPLICATION, user);
 
         if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
             // No change
@@ -55,26 +68,44 @@
 
         if (packageNames.contains(packageName)) {
             // Update the secure setting.
-            Settings.Secure.putString(context.getContentResolver(),
-                    Settings.Secure.DIALER_DEFAULT_APPLICATION, packageName);
+            Settings.Secure.putStringForUser(context.getContentResolver(),
+                    Settings.Secure.DIALER_DEFAULT_APPLICATION, packageName, user);
         }
     }
 
     /**
-     * Returns the installed dialer application that will be used to receive incoming calls, and is
-     * allowed to make emergency calls.
+     * Returns the installed dialer application for the current user that will be used to receive
+     * incoming calls, and is allowed to make emergency calls.
      *
      * The application will be returned in order of preference:
      * 1) User selected phone application (if still installed)
      * 2) Pre-installed system dialer (if not disabled)
      * 3) Null
      *
+     * The caller of this method needs to have permission to manage users on the device.
+     *
      * @hide
      * */
     public static String getDefaultDialerApplication(Context context) {
-        String defaultPackageName = Settings.Secure.getString(context.getContentResolver(),
-                Settings.Secure.DIALER_DEFAULT_APPLICATION);
+        return getDefaultDialerApplication(context, ActivityManager.getCurrentUser());
+    }
 
+    /**
+     * Returns the installed dialer application for the specified user that will be used to receive
+     * incoming calls, and is allowed to make emergency calls.
+     *
+     * The application will be returned in order of preference:
+     * 1) User selected phone application (if still installed)
+     * 2) Pre-installed system dialer (if not disabled)
+     * 3) Null
+     *
+     * The caller of this method needs to have permission to manage users on the device.
+     *
+     * @hide
+     * */
+    public static String getDefaultDialerApplication(Context context, int user) {
+        String defaultPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
+                Settings.Secure.DIALER_DEFAULT_APPLICATION, user);
 
         final List<String> packageNames = getInstalledDialerApplications(context);
 
diff --git a/tests/MemoryUsage/AndroidManifest.xml b/tests/MemoryUsage/AndroidManifest.xml
index 3932e5b..cd559c5 100644
--- a/tests/MemoryUsage/AndroidManifest.xml
+++ b/tests/MemoryUsage/AndroidManifest.xml
@@ -6,7 +6,9 @@
                      android:name="com.android.tests.memoryusage.MemoryUsageInstrumentation"
                      android:targetPackage="com.android.tests.memoryusage" />
 
+    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+
     <application android:label="Memory Usage Test">
         <uses-library android:name="android.test.runner" />
     </application>
-</manifest>
\ No newline at end of file
+</manifest>