Merge "add javadoc for SurfaceView {unL|l}ockCanvas{AndPost}()" into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 5a33193..6719066 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2306,28 +2306,23 @@
 
 package android.animation {
 
-  public abstract interface Animatable {
-    method public abstract long getDuration();
-    method public abstract android.animation.TimeInterpolator getInterpolator();
-    method public abstract long getStartDelay();
-    method public abstract android.animation.Animatable setDuration(long);
-    method public abstract void setInterpolator(android.animation.TimeInterpolator);
-    method public abstract void setStartDelay(long);
-  }
-
-  public abstract class Animator implements android.animation.Animatable java.lang.Cloneable {
+  public abstract class Animator implements java.lang.Cloneable {
     ctor public Animator();
     method public void addListener(android.animation.Animator.AnimatorListener);
     method public void cancel();
     method public android.animation.Animator clone();
     method public void end();
+    method public abstract long getDuration();
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
+    method public abstract long getStartDelay();
     method public abstract boolean isRunning();
     method public boolean isStarted();
     method public void removeAllListeners();
     method public void removeListener(android.animation.Animator.AnimatorListener);
     method public abstract android.animation.Animator setDuration(long);
+    method public abstract void setInterpolator(android.animation.TimeInterpolator);
+    method public abstract void setStartDelay(long);
     method public void setTarget(java.lang.Object);
     method public void setupEndValues();
     method public void setupStartValues();
@@ -5618,7 +5613,6 @@
     method public abstract java.lang.String[] fileList();
     method public abstract android.content.Context getApplicationContext();
     method public abstract android.content.pm.ApplicationInfo getApplicationInfo();
-    method public java.util.List<android.content.RestrictionEntry> getApplicationRestrictions();
     method public abstract android.content.res.AssetManager getAssets();
     method public abstract java.io.File getCacheDir();
     method public abstract java.lang.ClassLoader getClassLoader();
@@ -6205,8 +6199,9 @@
     field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
     field public static final java.lang.String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
     field public static final java.lang.String EXTRA_REPLACING = "android.intent.extra.REPLACING";
-    field public static final java.lang.String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions";
+    field public static final java.lang.String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle";
     field public static final java.lang.String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent";
+    field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
     field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
@@ -17556,7 +17551,7 @@
   }
 
   public class UserManager {
-    method public static synchronized android.os.UserManager get(android.content.Context);
+    method public android.os.Bundle getApplicationRestrictions(java.lang.String);
     method public long getSerialNumberForUser(android.os.UserHandle);
     method public int getUserCount();
     method public android.os.UserHandle getUserForSerialNumber(long);
@@ -30049,7 +30044,6 @@
     method public void setRelativeScrollPosition(int, int);
     method public deprecated void setRemoteAdapter(int, int, android.content.Intent);
     method public void setRemoteAdapter(int, android.content.Intent);
-    method public void setRemoteAdapter(int, java.util.ArrayList<android.widget.RemoteViews>, int);
     method public void setScrollPosition(int, int);
     method public void setShort(int, java.lang.String, short);
     method public void setString(int, java.lang.String, java.lang.String);
diff --git a/core/java/android/animation/Animatable.java b/core/java/android/animation/Animatable.java
deleted file mode 100644
index f9ccb4d..0000000
--- a/core/java/android/animation/Animatable.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2013 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.animation;
-
-/**
- * This interface is implemented by animation-related classes that expose
- * the ability to set and get duration, startDelay, and interpolators.
- */
-public interface Animatable {
-
-    /**
-     * The amount of time, in milliseconds, to delay processing the animation
-     * after the animation is started. The {@link #setDuration(long)} of the
-     * animation will not begin to elapse until after the startDelay has elapsed.
-     *
-     * @return the number of milliseconds to delay running the animation
-     */
-    long getStartDelay();
-
-    /**
-     * The amount of time, in milliseconds, to delay processing the animation
-     * after the animation is started. The {@link #setDuration(long)} of the
-     * animation will not begin to elapse until after the startDelay has elapsed.
-
-     * @param startDelay The amount of the delay, in milliseconds
-     */
-    void setStartDelay(long startDelay);
-
-    /**
-     * Sets the length of the animation.
-     *
-     * @param duration The length of the animation, in milliseconds.
-     */
-    Animatable setDuration(long duration);
-
-    /**
-     * Gets the duration of the animation.
-     *
-     * @return The length of the animation, in milliseconds.
-     */
-    long getDuration();
-
-    /**
-     * The time interpolator used in calculating the elapsed fraction of the
-     * animation. The interpolator determines whether the animation runs with
-     * linear or non-linear motion, such as acceleration and deceleration.
-     *
-     * @param value the interpolator to be used by this animation
-     */
-    void setInterpolator(TimeInterpolator value);
-
-    /**
-     * Returns the timing interpolator that this animation uses.
-     *
-     * @return The timing interpolator for this animation.
-     */
-    public TimeInterpolator getInterpolator();
-}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index da97d72..39eb8d6 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -22,21 +22,13 @@
  * This is the superclass for classes which provide basic support for animations which can be
  * started, ended, and have <code>AnimatorListeners</code> added to them.
  */
-public abstract class Animator implements Cloneable, Animatable {
+public abstract class Animator implements Cloneable {
 
     /**
      * The set of listeners to be sent events through the life of an animation.
      */
     ArrayList<AnimatorListener> mListeners = null;
 
-    @Override
-    public abstract Animator setDuration(long duration);
-
-    @Override
-    public TimeInterpolator getInterpolator() {
-        return null;
-    }
-
     /**
      * Starts this animation. If the animation has a nonzero startDelay, the animation will start
      * running after that delay elapses. A non-delayed animation will have its initial
@@ -77,6 +69,55 @@
     }
 
     /**
+     * The amount of time, in milliseconds, to delay processing the animation
+     * after {@link #start()} is called.
+     *
+     * @return the number of milliseconds to delay running the animation
+     */
+    public abstract long getStartDelay();
+
+    /**
+     * The amount of time, in milliseconds, to delay processing the animation
+     * after {@link #start()} is called.
+
+     * @param startDelay The amount of the delay, in milliseconds
+     */
+    public abstract void setStartDelay(long startDelay);
+
+    /**
+     * Sets the duration of the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     */
+    public abstract Animator setDuration(long duration);
+
+    /**
+     * Gets the duration of the animation.
+     *
+     * @return The length of the animation, in milliseconds.
+     */
+    public abstract long getDuration();
+
+    /**
+     * The time interpolator used in calculating the elapsed fraction of the
+     * animation. The interpolator determines whether the animation runs with
+     * linear or non-linear motion, such as acceleration and deceleration. The
+     * default value is {@link android.view.animation.AccelerateDecelerateInterpolator}.
+     *
+     * @param value the interpolator to be used by this animation
+     */
+    public abstract void setInterpolator(TimeInterpolator value);
+
+    /**
+     * Returns the timing interpolator that this animation uses.
+     *
+     * @return The timing interpolator for this animation.
+     */
+    public TimeInterpolator getInterpolator() {
+        return null;
+    }
+
+    /**
      * Returns whether this Animator is currently running (having been started and gone past any
      * initial startDelay period and not yet ended).
      *
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 7b07438..0d7f0a7 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -134,11 +134,6 @@
         }
     }
 
-    public List<RestrictionEntry> getApplicationRestrictions() {
-        return ((UserManager) getSystemService(USER_SERVICE))
-                .getApplicationRestrictions(getPackageName(), android.os.Process.myUserHandle());
-    }
-
     public void registerComponentCallbacks(ComponentCallbacks callback) {
         synchronized (mComponentCallbacks) {
             mComponentCallbacks.add(callback);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 03e241a..5bd28b9 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -293,15 +293,6 @@
     public abstract Context getApplicationContext();
 
     /**
-     * Returns the list of restrictions for the application, or null if there are no
-     * restrictions.
-     * @return
-     */
-    public List<RestrictionEntry> getApplicationRestrictions() {
-        return getApplicationContext().getApplicationRestrictions();
-    }
-
-    /**
      * Add a new {@link ComponentCallbacks} to the base application of the
      * Context, which will be called at the same times as the ComponentCallbacks
      * methods of activities and other components are called.  Note that you
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 97ad7dd..1ab1eb8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2417,11 +2417,16 @@
 
     /**
      * Broadcast to a specific application to query any supported restrictions to impose
-     * on restricted users. The response should contain an extra {@link #EXTRA_RESTRICTIONS},
+     * on restricted users. The broadcast intent contains an extra
+     * {@link #EXTRA_RESTRICTIONS_BUNDLE} with the currently persisted
+     * restrictions as a Bundle of key/value pairs. The value types can be Boolean, String or
+     * String[] depending on the restriction type.<p/>
+     * The response should contain an extra {@link #EXTRA_RESTRICTIONS_LIST},
      * which is of type <code>ArrayList&lt;RestrictionEntry&gt;</code>. It can also
      * contain an extra {@link #EXTRA_RESTRICTIONS_INTENT}, which is of type <code>Intent</code>.
      * The activity specified by that intent will be launched for a result which must contain
-     * the extra {@link #EXTRA_RESTRICTIONS}. The returned restrictions will be persisted.
+     * the extra {@link #EXTRA_RESTRICTIONS_LIST}. The keys and values of the returned restrictions
+     * will be persisted.
      * @see RestrictionEntry
      */
     public static final String ACTION_GET_RESTRICTION_ENTRIES =
@@ -3160,7 +3165,8 @@
         "android.intent.extra.ALLOW_MULTIPLE";
 
     /**
-     * The userHandle carried with broadcast intents related to addition, removal and switching of users
+     * The userHandle carried with broadcast intents related to addition, removal and switching of
+     * users
      * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
      * @hide
      */
@@ -3169,9 +3175,18 @@
 
     /**
      * Extra used in the response from a BroadcastReceiver that handles
-     * {@link #ACTION_GET_RESTRICTION_ENTRIES}.
+     * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is
+     * <code>ArrayList&lt;RestrictionEntry&gt;</code>.
      */
-    public static final String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions";
+    public static final String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
+
+    /**
+     * Extra sent in the intent to the BroadcastReceiver that handles
+     * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is a Bundle containing
+     * the restrictions as key/value pairs.
+     */
+    public static final String EXTRA_RESTRICTIONS_BUNDLE =
+            "android.intent.extra.restrictions_bundle";
 
     /**
      * Extra used in the response from a BroadcastReceiver that handles
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 2e8092a..a11358a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -42,7 +42,8 @@
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
     void setUserRestrictions(in Bundle restrictions, int userHandle);
-    void setApplicationRestrictions(in String packageName, in List<RestrictionEntry> entries,
+    void setApplicationRestrictions(in String packageName, in Bundle restrictions,
             int userHandle);
-    List<RestrictionEntry> getApplicationRestrictions(in String packageName, int userHandle);
+    Bundle getApplicationRestrictions(in String packageName);
+    Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index e580e2b..df065e9 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -142,6 +142,7 @@
 
     private static UserManager sInstance = null;
 
+    /** @hide */
     public synchronized static UserManager get(Context context) {
         if (sInstance == null) {
             sInstance = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -578,13 +579,29 @@
         return -1;
     }
 
+    /**
+     * Returns a Bundle containing any saved application restrictions for this user, for the
+     * given package name. Only an application with this package name can call this method.
+     * @param packageName the package name of the calling application
+     * @return a Bundle with the restrictions as key/value pairs, or null if there are no
+     * saved restrictions. The values can be of type Boolean, String or String[], depending
+     * on the restriction type, as defined by the application.
+     */
+    public Bundle getApplicationRestrictions(String packageName) {
+        try {
+            return mService.getApplicationRestrictions(packageName);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get application restrictions for package " + packageName);
+        }
+        return null;
+    }
 
     /**
      * @hide
      */
-    public List<RestrictionEntry> getApplicationRestrictions(String packageName, UserHandle user) {
+    public Bundle getApplicationRestrictions(String packageName, UserHandle user) {
         try {
-            return mService.getApplicationRestrictions(packageName, user.getIdentifier());
+            return mService.getApplicationRestrictionsForUser(packageName, user.getIdentifier());
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get application restrictions for user " + user.getIdentifier());
         }
@@ -594,10 +611,10 @@
     /**
      * @hide
      */
-    public void setApplicationRestrictions(String packageName, List<RestrictionEntry> entries,
+    public void setApplicationRestrictions(String packageName, Bundle restrictions,
             UserHandle user) {
         try {
-            mService.setApplicationRestrictions(packageName, entries, user.getIdentifier());
+            mService.setApplicationRestrictions(packageName, restrictions, user.getIdentifier());
         } catch (RemoteException re) {
             Log.w(TAG, "Could not set application restrictions for user " + user.getIdentifier());
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 88ee414..3df4e99 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3260,6 +3260,7 @@
         /**
          * This preference contains the string that shows for owner info on LockScreen.
          * @hide
+         * @deprecated
          */
         public static final String LOCK_SCREEN_OWNER_INFO = "lock_screen_owner_info";
 
@@ -3287,6 +3288,7 @@
         /**
          * This preference enables showing the owner info on LockScreen.
          * @hide
+         * @deprecated
          */
         public static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
             "lock_screen_owner_info_enabled";
@@ -4102,9 +4104,7 @@
             MOUNT_UMS_AUTOSTART,
             MOUNT_UMS_PROMPT,
             MOUNT_UMS_NOTIFY_ENABLED,
-            UI_NIGHT_MODE,
-            LOCK_SCREEN_OWNER_INFO,
-            LOCK_SCREEN_OWNER_INFO_ENABLED
+            UI_NIGHT_MODE
         };
 
         /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4fb2431..be26d20 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13380,18 +13380,32 @@
 
     /**
      * Sets a rectangular area on this view to which the view will be clipped
-     * it is drawn. Setting the value to null will remove the clip bounds
+     * when it is drawn. Setting the value to null will remove the clip bounds
      * and the view will draw normally, using its full bounds.
      *
      * @param clipBounds The rectangular area, in the local coordinates of
      * this view, to which future drawing operations will be clipped.
      */
     public void setClipBounds(Rect clipBounds) {
-        mClipBounds = clipBounds;
         if (clipBounds != null) {
-            invalidate(clipBounds);
+            if (clipBounds.equals(mClipBounds)) {
+                return;
+            }
+            if (mClipBounds == null) {
+                invalidate();
+                mClipBounds = new Rect(clipBounds);
+            } else {
+                invalidate(Math.min(mClipBounds.left, clipBounds.left),
+                        Math.min(mClipBounds.top, clipBounds.top),
+                        Math.max(mClipBounds.right, clipBounds.right),
+                        Math.max(mClipBounds.bottom, clipBounds.bottom));
+                mClipBounds.set(clipBounds);
+            }
         } else {
-            invalidate();
+            if (mClipBounds != null) {
+                invalidate();
+                mClipBounds = null;
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 98df064..528eadd 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -16,7 +16,6 @@
 
 package android.view;
 
-import android.animation.Animatable;
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.animation.TimeInterpolator;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 83e2e79..07e66b7 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2108,6 +2108,8 @@
      *      RemoteViews. This count cannot change during the life-cycle of a given widget, so this
      *      parameter should account for the maximum possible number of types that may appear in the
      *      See {@link Adapter#getViewTypeCount()}.
+     *
+     * @hide
      */
     public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
         addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 555c7c2..d3ead26 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -140,6 +140,10 @@
 
     public final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
 
+    private static final String LOCK_SCREEN_OWNER_INFO = Settings.Secure.LOCK_SCREEN_OWNER_INFO;
+    private static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
+            Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private DevicePolicyManager mDevicePolicyManager;
@@ -526,6 +530,22 @@
         }
     }
 
+    public void setOwnerInfo(String info, int userId) {
+        setString(LOCK_SCREEN_OWNER_INFO, info, userId);
+    }
+
+    public void setOwnerInfoEnabled(boolean enabled) {
+        setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled);
+    }
+
+    public String getOwnerInfo(int userId) {
+        return getString(LOCK_SCREEN_OWNER_INFO);
+    }
+
+    public boolean isOwnerInfoEnabled() {
+        return getBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, false);
+    }
+
     /**
      * Compute the password quality from the given password string.
      */
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 1ace23e..17f205d 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -339,23 +339,11 @@
 }
 
 TextLayoutShaper::TextLayoutShaper() {
-    init();
-
     mBuffer = hb_buffer_create();
 }
 
-void TextLayoutShaper::init() {
-    mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, SkTypeface::kNormal);
-}
-
-void TextLayoutShaper::unrefTypefaces() {
-    SkSafeUnref(mDefaultTypeface);
-}
-
 TextLayoutShaper::~TextLayoutShaper() {
     hb_buffer_destroy(mBuffer);
-
-    unrefTypefaces();
 }
 
 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
@@ -839,23 +827,27 @@
     }
 
     if (baseGlyphCount != 0) {
+        SkTypeface::Style style = SkTypeface::kNormal;
+        if (typeface != NULL) {
+            style = typeface->style();
+        }
         typeface = typefaceForScript(paint, typeface, hb_buffer_get_script(mBuffer));
         if (!typeface) {
             baseGlyphCount = 0;
-            typeface = mDefaultTypeface;
-            SkSafeRef(typeface);
+            typeface = SkFontHost::CreateTypeface(NULL, NULL, style);
 #if DEBUG_GLYPHS
             ALOGD("Using Default Typeface");
 #endif
         }
     } else {
         if (!typeface) {
-            typeface = mDefaultTypeface;
+            typeface = SkFontHost::CreateTypeface(NULL, NULL, SkTypeface::kNormal);
 #if DEBUG_GLYPHS
-            ALOGD("Using Default Typeface");
+            ALOGD("Using Default Typeface (normal style)");
 #endif
+        } else {
+            SkSafeRef(typeface);
         }
-        SkSafeRef(typeface);
     }
 
     mShapingPaint.setTypeface(typeface);
@@ -899,8 +891,6 @@
         hb_face_destroy(mCachedHBFaces.valueAt(i));
     }
     mCachedHBFaces.clear();
-    unrefTypefaces();
-    init();
 }
 
 TextLayoutEngine::TextLayoutEngine() {
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index f9b9900..5414a11 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -197,18 +197,10 @@
     SkPaint mShapingPaint;
 
     /**
-     * Skia default typeface to be returned if we cannot resolve script
-     */
-    SkTypeface* mDefaultTypeface;
-
-    /**
      * Cache of Harfbuzz faces
      */
     KeyedVector<SkFontID, hb_face_t*> mCachedHBFaces;
 
-    void init();
-    void unrefTypefaces();
-
     SkTypeface* typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
         hb_script_t script);
 
@@ -228,7 +220,6 @@
     hb_face_t* referenceCachedHBFace(SkTypeface* typeface);
 
     bool isComplexScript(hb_script_t script);
-
 }; // TextLayoutShaper
 
 /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6b4fe79..514178d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -918,7 +918,8 @@
     <permission android:name="android.permission.RECORD_AUDIO"
         android:permissionGroup="android.permission-group.MICROPHONE"
         android:protectionLevel="dangerous"
-        android:label="@string/permlab_recordAudio" />
+        android:label="@string/permlab_recordAudio"
+        android:description="@string/permdesc_recordAudio" />
 
 
     <!-- =========================================== -->
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 4a5e82e..61a0134 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -999,7 +999,7 @@
             if (mEventHandler != null) {
                 // signal new client
                 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
-                mEventHandler.dispatchMessage(
+                mEventHandler.sendMessage(
                         mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN,
                                 /*arg1*/ generationId, /*arg2, ignored*/ 0));
                 // send the information
@@ -1007,12 +1007,12 @@
                 mEventHandler.removeMessages(MSG_REQUEST_METADATA);
                 mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
                 mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
-                mEventHandler.dispatchMessage(
+                mEventHandler.sendMessage(
                         mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
-                mEventHandler.dispatchMessage(
+                mEventHandler.sendMessage(
                         mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
-                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
-                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
             }
         }
 
@@ -1020,7 +1020,7 @@
             // only post messages, we can't block here
             if (mEventHandler != null) {
                 mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
-                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(
                         MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
             }
         }
@@ -1028,7 +1028,7 @@
         public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
             // only post messages, we can't block here
             if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(
                         MSG_PLUG_DISPLAY, w, h, rcd));
             }
         }
@@ -1036,7 +1036,7 @@
         public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
             // only post messages, we can't block here
             if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(
                         MSG_UNPLUG_DISPLAY, rcd));
             }
         }
@@ -1044,7 +1044,7 @@
         public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) {
             // only post messages, we can't block here
             if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(
                         MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd));
             }
         }
@@ -1053,7 +1053,7 @@
             // only post messages, we can't block here
             if (mEventHandler != null) {
                 mEventHandler.removeMessages(MSG_SEEK_TO);
-                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(
                         MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */,
                         new Long(timeMs)));
             }
@@ -1145,6 +1145,7 @@
                     break;
                 case MSG_SEEK_TO:
                     onSeekTo(msg.arg1, ((Long)msg.obj).longValue());
+                    break;
                 default:
                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
             }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java
index 77359ff..9b58803 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java
@@ -23,12 +23,16 @@
 import android.content.Context;
 import android.os.BatteryManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Slog;
 import android.view.View;
 import android.widget.TextView;
 
@@ -37,6 +41,8 @@
 import java.lang.ref.WeakReference;
 
 import com.android.internal.R;
+import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockPatternUtils;
 
 /***
  * Manages a number of views inside of the given layout. See below for a list of widgets.
@@ -57,6 +63,8 @@
     static final int SECURITY_MESSAGE_DURATION = 5000;
     protected static final int FADE_DURATION = 750;
 
+    private static final String TAG = "KeyguardMessageArea";
+
     // are we showing battery information?
     boolean mShowingBatteryInfo = false;
 
@@ -82,6 +90,9 @@
 
     CharSequence mMessage;
     boolean mShowingMessage;
+    private CharSequence mSeparator;
+    private LockPatternUtils mLockPatternUtils;
+
     Runnable mClearMessageRunnable = new Runnable() {
         @Override
         public void run() {
@@ -156,8 +167,6 @@
         }
     };
 
-    private CharSequence mSeparator;
-
     public KeyguardMessageArea(Context context) {
         this(context, null);
     }
@@ -165,6 +174,8 @@
     public KeyguardMessageArea(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        mLockPatternUtils = new LockPatternUtils(context);
+
         // This is required to ensure marquee works
         setSelected(true);
 
@@ -228,11 +239,12 @@
 
     String getOwnerInfo() {
         ContentResolver res = getContext().getContentResolver();
-        final boolean ownerInfoEnabled = Settings.Secure.getIntForUser(res,
-                Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
-        return ownerInfoEnabled && !mShowingMessage ?
-                Settings.Secure.getStringForUser(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO,
-                        UserHandle.USER_CURRENT) : null;
+        String info = null;
+        final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled();
+        if (ownerInfoEnabled && !mShowingMessage) {
+            info = mLockPatternUtils.getOwnerInfo(mLockPatternUtils.getCurrentUser());
+        }
+        return info;
     }
 
     private CharSequence getChargeInfo(MutableInt icon) {
diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java
index e20a21f..41cc4d7 100644
--- a/services/java/com/android/server/LockSettingsService.java
+++ b/services/java/com/android/server/LockSettingsService.java
@@ -19,6 +19,11 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+
+import static android.content.Context.USER_SERVICE;
+import static android.Manifest.permission.READ_PROFILE;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
@@ -27,8 +32,10 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -40,6 +47,7 @@
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Keeps the lock pattern/password data and related settings for each user.
@@ -79,23 +87,52 @@
 
     private void migrateOldData() {
         try {
-            if (getString("migrated", null, 0) != null) {
-                // Already migrated
-                return;
+            // These Settings moved before multi-user was enabled, so we only have to do it for the
+            // root user.
+            if (getString("migrated", null, 0) == null) {
+                final ContentResolver cr = mContext.getContentResolver();
+                for (String validSetting : VALID_SETTINGS) {
+                    String value = Settings.Secure.getString(cr, validSetting);
+                    if (value != null) {
+                        setString(validSetting, value, 0);
+                    }
+                }
+                // No need to move the password / pattern files. They're already in the right place.
+                setString("migrated", "true", 0);
+                Slog.i(TAG, "Migrated lock settings to new location");
             }
 
-            final ContentResolver cr = mContext.getContentResolver();
-            for (String validSetting : VALID_SETTINGS) {
-                String value = Settings.Secure.getString(cr, validSetting);
-                if (value != null) {
-                    setString(validSetting, value, 0);
+            // These Settings changed after multi-user was enabled, hence need to be moved per user.
+            if (getString("migrated_user_specific", null, 0) == null) {
+                final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+                final ContentResolver cr = mContext.getContentResolver();
+                List<UserInfo> users = um.getUsers();
+                for (int user = 0; user < users.size(); user++) {
+                    int userId = users.get(user).getUserHandle().getIdentifier();
+                    for (String perUserSetting : MIGRATE_SETTINGS_PER_USER) {
+                        // Handle Strings
+                        String value = Settings.Secure.getStringForUser(cr, perUserSetting, userId);
+                        if (value != null) {
+                            setString(perUserSetting, value, userId);
+                            Settings.Secure.putStringForUser(cr, perUserSetting, "", userId);
+                            continue;
+                        }
+
+                        // Handle integers
+                        try {
+                            int ivalue = Settings.Secure.getIntForUser(cr, perUserSetting, userId);
+                            setLong(perUserSetting, ivalue, userId);
+                            Settings.Secure.putIntForUser(cr, perUserSetting, 0, userId);
+                        } catch (SettingNotFoundException e) {
+                        }
+                    }
                 }
+                // No need to move the password / pattern files. They're already in the right place.
+                setString("migrated_user_specific", "true", 0);
+                Slog.i(TAG, "Migrated per-user lock settings to new location");
             }
-            // No need to move the password / pattern files. They're already in the right place.
-            setString("migrated", "true", 0);
-            Slog.i(TAG, "Migrated lock settings to new location");
         } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to migrate old data");
+            Slog.e(TAG, "Unable to migrate old data", re);
         }
     }
 
@@ -115,12 +152,16 @@
         }
     }
 
-    private static final void checkReadPermission(int userId) {
+    private final void checkReadPermission(String requestedKey, int userId) {
         final int callingUid = Binder.getCallingUid();
-        if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID
-                && UserHandle.getUserId(callingUid) != userId) {
-            throw new SecurityException("uid=" + callingUid
-                    + " not authorized to read settings of user " + userId);
+        for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) {
+            String key = READ_PROFILE_PROTECTED_SETTINGS[i];
+            if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("uid=" + callingUid
+                        + " needs permission " + READ_PROFILE + " to read "
+                        + requestedKey + " for user " + userId);
+            }
         }
     }
 
@@ -147,7 +188,7 @@
 
     @Override
     public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
-        //checkReadPermission(userId);
+        checkReadPermission(key, userId);
 
         String value = readFromDb(key, null, userId);
         return TextUtils.isEmpty(value) ?
@@ -156,7 +197,7 @@
 
     @Override
     public long getLong(String key, long defaultValue, int userId) throws RemoteException {
-        //checkReadPermission(userId);
+        checkReadPermission(key, userId);
 
         String value = readFromDb(key, null, userId);
         return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
@@ -164,7 +205,7 @@
 
     @Override
     public String getString(String key, String defaultValue, int userId) throws RemoteException {
-        //checkReadPermission(userId);
+        checkReadPermission(key, userId);
 
         return readFromDb(key, defaultValue, userId);
     }
@@ -404,5 +445,13 @@
         Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
         Secure.LOCK_PATTERN_VISIBLE,
         Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
-        };
+    };
+
+    private static final String[] MIGRATE_SETTINGS_PER_USER = new String[] {
+        Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
+        Secure.LOCK_SCREEN_OWNER_INFO
+    };
+
+    // These are protected with a read permission
+    private static final String[] READ_PROFILE_PROTECTED_SETTINGS = MIGRATE_SETTINGS_PER_USER;
 }
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index fa18e76..3bebf91 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -1676,8 +1676,12 @@
                 .getSystemService(Context.AUDIO_SERVICE);
 
                 // sound
+
+                // should we use the default notification sound? (indicated either by DEFAULT_SOUND
+                // or because notification.sound is pointing at Settings.System.NOTIFICATION_SOUND)
                 final boolean useDefaultSound =
-                    (notification.defaults & Notification.DEFAULT_SOUND) != 0;
+                       (notification.defaults & Notification.DEFAULT_SOUND) != 0
+                    || Settings.System.DEFAULT_NOTIFICATION_URI.equals(notification.sound);
 
                 Uri soundUri = null;
                 boolean hasValidSound = false;
diff --git a/services/java/com/android/server/am/NativeCrashListener.java b/services/java/com/android/server/am/NativeCrashListener.java
index 0688c50..de439d7 100644
--- a/services/java/com/android/server/am/NativeCrashListener.java
+++ b/services/java/com/android/server/am/NativeCrashListener.java
@@ -43,13 +43,14 @@
 class NativeCrashListener extends Thread {
     static final String TAG = "NativeCrashListener";
     static final boolean DEBUG = false;
+    static final boolean MORE_DEBUG = DEBUG && false;
 
     // Must match the path defined in debuggerd.c.
     static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
 
     // Use a short timeout on socket operations and abandon the connection
     // on hard errors
-    static final long SOCKET_TIMEOUT_MILLIS = 1000;  // 1 second
+    static final long SOCKET_TIMEOUT_MILLIS = 2000;  // 2 seconds
 
     final ActivityManagerService mAm;
 
@@ -124,9 +125,9 @@
                 InetSocketAddress peer = new InetSocketAddress();
                 FileDescriptor peerFd = null;
                 try {
-                    if (DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
+                    if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
                     peerFd = Libcore.os.accept(serverFd, peer);
-                    if (DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
+                    if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
                     if (peerFd != null) {
                         // Only the superuser is allowed to talk to us over this socket
                         StructUcred credentials =
@@ -145,7 +146,12 @@
                     if (peerFd != null) {
                         try {
                             Libcore.os.write(peerFd, ackSignal, 0, 1);
-                        } catch (Exception e) { /* we don't care about failures here */ }
+                        } catch (Exception e) {
+                            /* we don't care about failures here */
+                            if (MORE_DEBUG) {
+                                Slog.d(TAG, "Exception writing ack: " + e.getMessage());
+                            }
+                        }
                     }
                 }
             }
@@ -183,7 +189,7 @@
 
     // Read the crash report from the debuggerd connection
     void consumeNativeCrashData(FileDescriptor fd) {
-        if (DEBUG) Slog.i(TAG, "debuggerd connected");
+        if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
         final byte[] buf = new byte[4096];
         final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
 
@@ -218,7 +224,7 @@
                         // get some data
                         bytes = Libcore.os.read(fd, buf, 0, buf.length);
                         if (bytes > 0) {
-                            if (DEBUG) {
+                            if (MORE_DEBUG) {
                                 String s = new String(buf, 0, bytes, "UTF-8");
                                 Slog.v(TAG, "READ=" + bytes + "> " + s);
                             }
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index df90a56..11c6dab 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -88,8 +88,13 @@
     private static final String TAG_ENTRY = "entry";
     private static final String TAG_VALUE = "value";
     private static final String ATTR_KEY = "key";
+    private static final String ATTR_VALUE_TYPE = "type";
     private static final String ATTR_MULTIPLE = "m";
 
+    private static final String ATTR_TYPE_STRING_ARRAY = "sa";
+    private static final String ATTR_TYPE_STRING = "s";
+    private static final String ATTR_TYPE_BOOLEAN = "b";
+
     private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
     private static final String USER_PHOTO_FILENAME = "photo.png";
@@ -965,7 +970,12 @@
     }
 
     @Override
-    public List<RestrictionEntry> getApplicationRestrictions(String packageName, int userId) {
+    public Bundle getApplicationRestrictions(String packageName) {
+        return getApplicationRestrictionsForUser(packageName, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
         if (UserHandle.getCallingUserId() != userId
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
             checkManageUsersPermission("Only system can get restrictions for other users/apps");
@@ -977,7 +987,7 @@
     }
 
     @Override
-    public void setApplicationRestrictions(String packageName, List<RestrictionEntry> entries,
+    public void setApplicationRestrictions(String packageName, Bundle restrictions,
             int userId) {
         if (UserHandle.getCallingUserId() != userId
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
@@ -985,7 +995,7 @@
         }
         synchronized (mPackagesLock) {
             // Write the restrictions to XML
-            writeApplicationRestrictionsLocked(packageName, entries, userId);
+            writeApplicationRestrictionsLocked(packageName, restrictions, userId);
         }
     }
 
@@ -1001,9 +1011,9 @@
         }
     }
 
-    private List<RestrictionEntry> readApplicationRestrictionsLocked(String packageName,
+    private Bundle readApplicationRestrictionsLocked(String packageName,
             int userId) {
-        final ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
+        final Bundle restrictions = new Bundle();
         final ArrayList<String> values = new ArrayList<String>();
 
         FileInputStream fis = null;
@@ -1023,12 +1033,13 @@
             if (type != XmlPullParser.START_TAG) {
                 Slog.e(LOG_TAG, "Unable to read restrictions file "
                         + restrictionsFile.getBaseFile());
-                return entries;
+                return restrictions;
             }
 
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
                     String key = parser.getAttributeValue(null, ATTR_KEY);
+                    String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
                     String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE);
                     if (multiple != null) {
                         int count = Integer.parseInt(multiple);
@@ -1041,14 +1052,13 @@
                         }
                         String [] valueStrings = new String[values.size()];
                         values.toArray(valueStrings);
-                        Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + valueStrings);
-                        RestrictionEntry entry = new RestrictionEntry(key, valueStrings);
-                        entries.add(entry);
+                        restrictions.putStringArray(key, valueStrings);
+                    } else if (ATTR_TYPE_BOOLEAN.equals(valType)) {
+                        restrictions.putBoolean(key, Boolean.parseBoolean(
+                                parser.nextText().trim()));
                     } else {
                         String value = parser.nextText().trim();
-                        Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + value);
-                        RestrictionEntry entry = new RestrictionEntry(key, value);
-                        entries.add(entry);
+                        restrictions.putString(key, value);
                     }
                 }
             }
@@ -1063,11 +1073,11 @@
                 }
             }
         }
-        return entries;
+        return restrictions;
     }
 
     private void writeApplicationRestrictionsLocked(String packageName,
-            List<RestrictionEntry> entries, int userId) {
+            Bundle restrictions, int userId) {
         FileOutputStream fos = null;
         AtomicFile restrictionsFile = new AtomicFile(
                 new File(Environment.getUserSystemDirectory(userId),
@@ -1084,18 +1094,24 @@
 
             serializer.startTag(null, TAG_RESTRICTIONS);
 
-            for (RestrictionEntry entry : entries) {
+            for (String key : restrictions.keySet()) {
+                Object value = restrictions.get(key);
                 serializer.startTag(null, TAG_ENTRY);
-                serializer.attribute(null, ATTR_KEY, entry.getKey());
-                if (entry.getSelectedString() != null || entry.getAllSelectedStrings() == null) {
-                    String value = entry.getSelectedString();
-                    serializer.text(value != null ? value : "");
+                serializer.attribute(null, ATTR_KEY, key);
+
+                if (value instanceof Boolean) {
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
+                    serializer.text(value.toString());
+                } else if (value == null || value instanceof String) {
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
+                    serializer.text(value != null ? (String) value : "");
                 } else {
-                    String[] values = entry.getAllSelectedStrings();
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
+                    String[] values = (String[]) value;
                     serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length));
-                    for (String value : values) {
+                    for (String choice : values) {
                         serializer.startTag(null, TAG_VALUE);
-                        serializer.text(value != null ? value : "");
+                        serializer.text(choice != null ? choice : "");
                         serializer.endTag(null, TAG_VALUE);
                     }
                 }
diff --git a/tests/CanvasCompare/AndroidManifest.xml b/tests/CanvasCompare/AndroidManifest.xml
index 1cb8c37..b55e290 100644
--- a/tests/CanvasCompare/AndroidManifest.xml
+++ b/tests/CanvasCompare/AndroidManifest.xml
@@ -15,8 +15,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.test.hwuicompare" >
 
-    <!-- for perfhud -->
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application
         android:label="@string/app_name"
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java
index e0ff1dc..1ed4723 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java
@@ -16,11 +16,20 @@
 
 package com.android.test.hwuicompare;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.TreeSet;
 
-import com.android.test.hwuicompare.R;
+import org.json.JSONException;
+import org.json.JSONObject;
 
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Trace;
 import android.util.Log;
 import android.widget.ImageView;
@@ -31,20 +40,31 @@
     private static final float ERROR_DISPLAY_THRESHOLD = 0.01f;
     protected static final boolean DRAW_BITMAPS = false;
 
+    /**
+     * Threshold of error change required to consider a test regressed/improved
+     */
+    private static final float ERROR_CHANGE_THRESHOLD = 0.001f;
+
+    private static final float[] ERROR_CUTOFFS = {
+            0, 0.005f, 0.01f, 0.02f, 0.05f, 0.1f, 0.25f, 0.5f, 1f, 2f
+    };
+
+    private final float[] mErrorRates = new float[ERROR_CUTOFFS.length];
+    private float mTotalTests = 0;
+    private float mTotalError = 0;
+    private int mTestsRegressed = 0;
+    private int mTestsImproved = 0;
+
     private ImageView mSoftwareImageView = null;
     private ImageView mHardwareImageView = null;
 
-    private static final float[] ERROR_CUTOFFS = {0, 0.005f, 0.01f, 0.02f, 0.05f, 0.1f, 0.25f, 0.5f, 1f, 2f};
-    private float[] mErrorRates = new float[ERROR_CUTOFFS.length];
-    private float mTotalTests = 0;
-    private float mTotalError = 0;
 
-    public abstract static class TestCallback {
+    public abstract static class FinalCallback {
         abstract void report(String name, float value);
-        void complete() {}
+        void complete() {};
     }
 
-    private ArrayList<TestCallback> mTestCallbacks = new ArrayList<TestCallback>();
+    private final ArrayList<FinalCallback> mFinalCallbacks = new ArrayList<FinalCallback>();
 
     Runnable mRunnable = new Runnable() {
         @Override
@@ -64,32 +84,11 @@
             float error = mErrorCalculator.calcErrorRS(mSoftwareBitmap, mHardwareBitmap);
             Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
 
-            if (error > ERROR_DISPLAY_THRESHOLD) {
-                String modname = "";
-                for (String s : DisplayModifier.getLastAppliedModifications()) {
-                    modname = modname.concat(s + ".");
-                }
-                Log.d(LOG_TAG, String.format("error for %s was %2.9f", modname, error));
-            }
-            for (int i = 0; i < ERROR_CUTOFFS.length; i++) {
-                if (error <= ERROR_CUTOFFS[i]) break;
-                mErrorRates[i]++;
-            }
-            mTotalError += error;
-            mTotalTests++;
+            final String[] modifierNames = DisplayModifier.getLastAppliedModifications();
+            handleError(modifierNames, error);
 
             if (DisplayModifier.step()) {
-                for (TestCallback c : mTestCallbacks) {
-                    c.report("averageError", (mTotalError / mTotalTests));
-                    for (int i = 1; i < ERROR_CUTOFFS.length; i++) {
-                        c.report(String.format("error over %1.3f", ERROR_CUTOFFS[i]),
-                                mErrorRates[i]/mTotalTests);
-                    }
-                    c.complete();
-                }
-
-                Toast.makeText(getApplicationContext(), "done!", Toast.LENGTH_SHORT).show();
-                finish();
+                finishTest();
             } else {
                 mHardwareView.invalidate();
                 if (DRAW_BITMAPS) {
@@ -116,11 +115,186 @@
         mHardwareImageView = (ImageView) findViewById(R.id.hardware_image_view);
 
         onCreateCommon(mRunnable);
-        mTestCallbacks.add(new TestCallback() {
+        beginTest();
+    }
+
+    private static class TestResult {
+        TestResult(String label, float error) {
+            mLabel = label;
+            mTotalError = error;
+            mCount = 1;
+        }
+        public void addInto(float error) {
+            mTotalError += error;
+            mCount++;
+        }
+        public float getAverage() {
+            return mTotalError / mCount;
+        }
+        final String mLabel;
+        float mTotalError;
+        int mCount;
+    }
+
+    JSONObject mOutputJson = null;
+    JSONObject mInputJson = null;
+    final HashMap<String, TestResult> mModifierResults = new HashMap<String, TestResult>();
+    final HashMap<String, TestResult> mIndividualResults = new HashMap<String, TestResult>();
+    final HashMap<String, TestResult> mModifierDiffResults = new HashMap<String, TestResult>();
+    final HashMap<String, TestResult> mIndividualDiffResults = new HashMap<String, TestResult>();
+    private void beginTest() {
+        mFinalCallbacks.add(new FinalCallback() {
+            @Override
             void report(String name, float value) {
                 Log.d(LOG_TAG, name + " " + value);
             };
         });
+
+        File inputFile = new File(Environment.getExternalStorageDirectory(),
+                "CanvasCompareInput.json");
+        if (inputFile.exists() && inputFile.canRead() && inputFile.length() > 0) {
+            try {
+                FileInputStream inputStream = new FileInputStream(inputFile);
+                Log.d(LOG_TAG, "Parsing input file...");
+                StringBuffer content = new StringBuffer((int)inputFile.length());
+                byte[] buffer = new byte[1024];
+                while (inputStream.read(buffer) != -1) {
+                    content.append(new String(buffer));
+                }
+                mInputJson = new JSONObject(content.toString());
+                inputStream.close();
+                Log.d(LOG_TAG, "Parsed input file with " + mInputJson.length() + " entries");
+            } catch (JSONException e) {
+                Log.e(LOG_TAG, "error parsing input json", e);
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "error reading input json from sd", e);
+            }
+        }
+
+        mOutputJson = new JSONObject();
+    }
+
+    private static void logTestResultHash(String label, HashMap<String, TestResult> map) {
+        Log.d(LOG_TAG, "---------------");
+        Log.d(LOG_TAG, label + ":");
+        Log.d(LOG_TAG, "---------------");
+        TreeSet<TestResult> set = new TreeSet<TestResult>(new Comparator<TestResult>() {
+            @Override
+            public int compare(TestResult lhs, TestResult rhs) {
+                if (lhs == rhs) return 0; // don't need to worry about complex equality
+
+                int cmp = Float.compare(lhs.getAverage(), rhs.getAverage());
+                if (cmp != 0) {
+                    return cmp;
+                }
+                return lhs.mLabel.compareTo(rhs.mLabel);
+            }
+        });
+
+        for (TestResult t : map.values()) {
+            set.add(t);
+        }
+
+        for (TestResult t : set.descendingSet()) {
+            if (Math.abs(t.getAverage()) > ERROR_DISPLAY_THRESHOLD) {
+                Log.d(LOG_TAG, String.format("%2.4f : %s", t.getAverage(), t.mLabel));
+            }
+        }
+        Log.d(LOG_TAG, "");
+    }
+
+    private void finishTest() {
+        for (FinalCallback c : mFinalCallbacks) {
+            c.report("averageError", (mTotalError / mTotalTests));
+            for (int i = 1; i < ERROR_CUTOFFS.length; i++) {
+                c.report(String.format("tests with error over %1.3f", ERROR_CUTOFFS[i]),
+                        mErrorRates[i]);
+            }
+            if (mInputJson != null) {
+                c.report("tests regressed", mTestsRegressed);
+                c.report("tests improved", mTestsImproved);
+            }
+            c.complete();
+        }
+
+        try {
+            if (mOutputJson != null) {
+                String outputString = mOutputJson.toString(4);
+                File outputFile = new File(Environment.getExternalStorageDirectory(),
+                        "CanvasCompareOutput.json");
+                FileOutputStream outputStream = new FileOutputStream(outputFile);
+                outputStream.write(outputString.getBytes());
+                outputStream.close();
+                Log.d(LOG_TAG, "Saved output file with " + mOutputJson.length() + " entries");
+            }
+        } catch (JSONException e) {
+            Log.e(LOG_TAG, "error during JSON stringify", e);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "error storing JSON output on sd", e);
+        }
+
+        logTestResultHash("Modifier change vs previous", mModifierDiffResults);
+        logTestResultHash("Invidual test change vs previous", mIndividualDiffResults);
+        logTestResultHash("Modifier average test results", mModifierResults);
+        logTestResultHash("Individual test results", mIndividualResults);
+
+        Toast.makeText(getApplicationContext(), "done!", Toast.LENGTH_SHORT).show();
+        finish();
+    }
+
+    /**
+     * Inserts the error value into all TestResult objects, associated with each of its modifiers
+     */
+    private static void addForAllModifiers(String fullName, float error, String[] modifierNames,
+            HashMap<String, TestResult> modifierResults) {
+        for (String modifierName : modifierNames) {
+            TestResult r = modifierResults.get(fullName);
+            if (r == null) {
+                modifierResults.put(modifierName, new TestResult(modifierName, error));
+            } else {
+                r.addInto(error);
+            }
+        }
+    }
+
+    private void handleError(final String[] modifierNames, final float error) {
+        String fullName = "";
+        for (String s : modifierNames) {
+            fullName = fullName.concat("." + s);
+        }
+        fullName = fullName.substring(1);
+
+        float deltaError = 0;
+        if (mInputJson != null) {
+            try {
+                deltaError = error - (float)mInputJson.getDouble(fullName);
+            } catch (JSONException e) {
+                Log.w(LOG_TAG, "Warning: unable to read from input json", e);
+            }
+            if (deltaError > ERROR_CHANGE_THRESHOLD) mTestsRegressed++;
+            if (deltaError < -ERROR_CHANGE_THRESHOLD) mTestsImproved++;
+            mIndividualDiffResults.put(fullName, new TestResult(fullName, deltaError));
+            addForAllModifiers(fullName, deltaError, modifierNames, mModifierDiffResults);
+        }
+
+        mIndividualResults.put(fullName, new TestResult(fullName, error));
+        addForAllModifiers(fullName, error, modifierNames, mModifierResults);
+
+        try {
+            if (mOutputJson != null) {
+                mOutputJson.put(fullName, error);
+            }
+        } catch (JSONException e) {
+            Log.e(LOG_TAG, "exception during JSON recording", e);
+            mOutputJson = null;
+        }
+
+        for (int i = 0; i < ERROR_CUTOFFS.length; i++) {
+            if (error <= ERROR_CUTOFFS[i]) break;
+            mErrorRates[i]++;
+        }
+        mTotalError += error;
+        mTotalTests++;
     }
 
     @Override
@@ -130,7 +304,7 @@
     }
 
     // FOR TESTING
-    public void setCallback(TestCallback c) {
-        mTestCallbacks.add(c);
+    public void setFinalCallback(FinalCallback c) {
+        mFinalCallbacks.add(c);
     }
 }
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java
index 6ea8237..1ff153c 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java
@@ -1,6 +1,6 @@
 package com.android.test.hwuicompare;
 
-import com.android.test.hwuicompare.AutomaticActivity.TestCallback;
+import com.android.test.hwuicompare.AutomaticActivity.FinalCallback;
 
 import android.os.Bundle;
 import android.test.ActivityInstrumentationTestCase2;
@@ -18,7 +18,7 @@
         super.setUp();
         mBundle = new Bundle();
         mActivity = getActivity();
-        mActivity.setCallback(new TestCallback() {
+        mActivity.setFinalCallback(new FinalCallback() {
 
             @Override
             void report(String key, float value) {