Merge "Add functions for setting/getting phone accounts." into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 6fbd40b..22755b6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -905,6 +905,8 @@
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int navigationBarColor = 16843858; // 0x1010452
+    field public static final int navigationContentDescription = 16843970; // 0x10104c2
+    field public static final int navigationIcon = 16843969; // 0x10104c1
     field public static final int navigationMode = 16843471; // 0x10102cf
     field public static final int negativeButtonText = 16843254; // 0x10101f6
     field public static final int nestedScrollingEnabled = 16843830; // 0x1010436
@@ -1757,6 +1759,7 @@
     field public static final int list = 16908298; // 0x102000a
     field public static final int mask = 16908353; // 0x1020041
     field public static final int message = 16908299; // 0x102000b
+    field public static final int navigationBarBackground = 16908355; // 0x1020043
     field public static final int paste = 16908322; // 0x1020022
     field public static final int primary = 16908300; // 0x102000c
     field public static final int progress = 16908301; // 0x102000d
@@ -1765,6 +1768,7 @@
     field public static final int selectTextMode = 16908333; // 0x102002d
     field public static final int selectedIcon = 16908302; // 0x102000e
     field public static final int startSelectingText = 16908328; // 0x1020028
+    field public static final int statusBarBackground = 16908354; // 0x1020042
     field public static final int stopSelectingText = 16908329; // 0x1020029
     field public static final int summary = 16908304; // 0x1020010
     field public static final int switchInputMethod = 16908324; // 0x1020024
@@ -5746,7 +5750,7 @@
   }
 
   public final class UsageStatsManager {
-    method public android.util.ArrayMap<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long);
+    method public java.util.Map<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long);
     method public android.app.usage.UsageEvents queryEvents(long, long);
     method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
     field public static final int INTERVAL_BEST = 4; // 0x4
@@ -7285,8 +7289,8 @@
     method public void registerComponentCallbacks(android.content.ComponentCallbacks);
     method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter);
     method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler);
-    method public abstract void removeStickyBroadcast(android.content.Intent);
-    method public abstract void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
+    method public abstract deprecated void removeStickyBroadcast(android.content.Intent);
+    method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public abstract void revokeUriPermission(android.net.Uri, int);
     method public abstract void sendBroadcast(android.content.Intent);
     method public abstract void sendBroadcast(android.content.Intent, java.lang.String);
@@ -7295,10 +7299,10 @@
     method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String);
     method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public abstract void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public abstract void sendStickyBroadcast(android.content.Intent);
-    method public abstract void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
-    method public abstract void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public abstract void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+    method public abstract deprecated void sendStickyBroadcast(android.content.Intent);
+    method public abstract deprecated void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
+    method public abstract deprecated void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+    method public abstract deprecated void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public abstract void setTheme(int);
     method public abstract deprecated void setWallpaper(android.graphics.Bitmap) throws java.io.IOException;
     method public abstract deprecated void setWallpaper(java.io.InputStream) throws java.io.IOException;
@@ -17197,10 +17201,6 @@
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
-  public abstract interface NetworkBoundURLFactory {
-    method public abstract java.net.URL getBoundURL(android.net.Network, java.net.URL) throws java.net.MalformedURLException;
-  }
-
   public final class NetworkCapabilities implements android.os.Parcelable {
     ctor public NetworkCapabilities(android.net.NetworkCapabilities);
     method public int describeContents();
@@ -27196,16 +27196,14 @@
 package android.service.voice {
 
   public class AlwaysOnHotwordDetector {
-    method public android.content.Intent getManageIntent(int);
+    method public android.content.Intent createIntentToEnroll();
+    method public android.content.Intent createIntentToReEnroll();
+    method public android.content.Intent createIntentToUnEnroll();
     method public int getSupportedRecognitionModes();
     method public boolean startRecognition(int);
     method public boolean stopRecognition();
-    field public static final int MANAGE_ACTION_ENROLL = 0; // 0x0
-    field public static final int MANAGE_ACTION_RE_ENROLL = 1; // 0x1
-    field public static final int MANAGE_ACTION_UN_ENROLL = 2; // 0x2
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
-    field public static final int RECOGNITION_FLAG_NONE = 0; // 0x0
     field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
     field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
     field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
@@ -27214,7 +27212,8 @@
     field public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
   }
 
-  public static abstract interface AlwaysOnHotwordDetector.Callback {
+  public static abstract class AlwaysOnHotwordDetector.Callback {
+    ctor public AlwaysOnHotwordDetector.Callback();
     method public abstract void onAvailabilityChanged(int);
     method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.EventPayload);
     method public abstract void onError();
@@ -28421,6 +28420,7 @@
   }
 
   public static class PhoneAccount.Builder {
+    ctor public PhoneAccount.Builder();
     method public android.telecomm.PhoneAccount build();
     method public android.telecomm.PhoneAccount.Builder withAccountHandle(android.telecomm.PhoneAccountHandle);
     method public android.telecomm.PhoneAccount.Builder withCapabilities(int);
@@ -39590,22 +39590,6 @@
 
 }
 
-package com.android.internal.telecomm {
-
-  public abstract interface RemoteServiceCallback implements android.os.IInterface {
-    method public abstract void onError() throws android.os.RemoteException;
-    method public abstract void onResult(java.util.List<android.content.ComponentName>, java.util.List<android.os.IBinder>) throws android.os.RemoteException;
-  }
-
-  public static abstract class RemoteServiceCallback.Stub extends android.os.Binder implements com.android.internal.telecomm.RemoteServiceCallback {
-    ctor public RemoteServiceCallback.Stub();
-    method public android.os.IBinder asBinder();
-    method public static com.android.internal.telecomm.RemoteServiceCallback asInterface(android.os.IBinder);
-    method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
-  }
-
-}
-
 package com.android.internal.util {
 
   public abstract interface Predicate {
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 74ccbc2..6e77e13 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -161,17 +161,17 @@
     LOG_ALWAYS_FATAL_IF((numChars >= PATH_MAX || numChars < 0),
             "Error constructing dalvik cache : %s", strerror(errno));
 
-    int result = mkdir(dalvikCacheDir, 0771);
+    int result = mkdir(dalvikCacheDir, 0711);
     LOG_ALWAYS_FATAL_IF((result < 0 && errno != EEXIST),
             "Error creating cache dir %s : %s", dalvikCacheDir, strerror(errno));
 
     // We always perform these steps because the directory might
     // already exist, with wider permissions and a different owner
     // than we'd like.
-    result = chown(dalvikCacheDir, AID_SYSTEM, AID_SYSTEM);
+    result = chown(dalvikCacheDir, AID_ROOT, AID_ROOT);
     LOG_ALWAYS_FATAL_IF((result < 0), "Error changing dalvik-cache ownership : %s", strerror(errno));
 
-    result = chmod(dalvikCacheDir, 0771);
+    result = chmod(dalvikCacheDir, 0711);
     LOG_ALWAYS_FATAL_IF((result < 0),
             "Error changing dalvik-cache permissions : %s", strerror(errno));
 }
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index d9b40b1..da34094 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -866,6 +866,7 @@
 
     private void runInstall() {
         int installFlags = PackageManager.INSTALL_ALL_USERS;
+        int userId = UserHandle.USER_ALL;
         String installerPackageName = null;
 
         String opt;
@@ -909,6 +910,13 @@
                 }
             } else if (opt.equals("--abi")) {
                 abi = checkAbiArgument(nextOptionData());
+            } else if (opt.equals("--user")) {
+                userId = Integer.parseInt(nextOptionData());
+                if (userId == UserHandle.USER_ALL) {
+                    installFlags |= PackageManager.INSTALL_ALL_USERS;
+                } else {
+                    installFlags &= ~PackageManager.INSTALL_ALL_USERS;
+                }
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
@@ -953,8 +961,8 @@
             VerificationParams verificationParams = new VerificationParams(verificationURI,
                     originatingURI, referrerURI, VerificationParams.NO_UID, null);
 
-            mPm.installPackage(apkFilePath, obs.getBinder(), installFlags, installerPackageName,
-                    verificationParams, abi);
+            mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags, installerPackageName,
+                    verificationParams, abi, userId);
 
             synchronized (obs) {
                 while (!obs.finished) {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 214f50c..dbd180f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.net.Uri;
+import android.os.Bundle;
 import android.service.notification.Condition;
 import android.service.notification.IConditionListener;
 import android.service.notification.IConditionProvider;
@@ -58,13 +59,15 @@
     void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
     void cancelNotificationsFromListener(in INotificationListener token, in String[] keys);
 
-    ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys);
+    ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim);
     void requestHintsFromListener(in INotificationListener token, int hints);
     int getHintsFromListener(in INotificationListener token);
     void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
     int getInterruptionFilterFromListener(in INotificationListener token);
+    void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
 
     ComponentName getEffectsSuppressor();
+    boolean matchesCallFilter(in Bundle extras);
 
     ZenModeConfig getZenModeConfig();
     boolean setZenModeConfig(in ZenModeConfig config);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e1dc8bf..966d2ce 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1424,6 +1424,8 @@
             extras.remove(Notification.EXTRA_LARGE_ICON);
             extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
             extras.remove(Notification.EXTRA_PICTURE);
+            // Prevent light notifications from being rebuilt.
+            extras.remove(Builder.EXTRA_NEEDS_REBUILD);
         }
     }
 
@@ -1868,6 +1870,15 @@
         private Notification mRebuildNotification = null;
 
         /**
+         * Whether the build notification has three lines. This is used to make the top padding for
+         * both the contracted and expanded layout consistent.
+         *
+         * <p>
+         * This field is only valid during the build phase.
+         */
+        private boolean mHasThreeLines;
+
+        /**
          * Constructs a new Builder with the defaults:
          *
 
@@ -2564,19 +2575,23 @@
             return this;
         }
 
-        private Bitmap getProfileBadge() {
+        private Drawable getProfileBadgeDrawable() {
             // Note: This assumes that the current user can read the profile badge of the
             // originating user.
             UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            Drawable badge = userManager.getBadgeForUser(new UserHandle(mOriginatingUserId), 0);
+            return userManager.getBadgeForUser(new UserHandle(mOriginatingUserId), 0);
+        }
+
+        private Bitmap getProfileBadge() {
+            Drawable badge = getProfileBadgeDrawable();
             if (badge == null) {
                 return null;
             }
-            final int width = badge.getIntrinsicWidth();
-            final int height = badge.getIntrinsicHeight();
-            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            final int size = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.notification_badge_size);
+            Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
             Canvas canvas = new Canvas(bitmap);
-            badge.setBounds(0, 0, width, height);
+            badge.setBounds(0, 0, size, size);
             badge.draw(canvas);
             return bitmap;
         }
@@ -2602,6 +2617,12 @@
             return false;
         }
 
+        private void shrinkLine3Text(RemoteViews contentView) {
+            float subTextSize = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.notification_subtext_size);
+            contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
+        }
+
         private RemoteViews applyStandardTemplate(int resId) {
             RemoteViews contentView = new BuilderRemoteViews(mContext.getPackageName(),
                     mOriginatingUserId, resId);
@@ -2674,10 +2695,7 @@
             if (showLine2) {
 
                 // need to shrink all the type to make sure everything fits
-                final Resources res = mContext.getResources();
-                final float subTextSize = res.getDimensionPixelSize(
-                        R.dimen.notification_subtext_size);
-                contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
+                shrinkLine3Text(contentView);
             }
 
             if (mWhen != 0 && mShowWhen) {
@@ -2696,7 +2714,7 @@
 
             // Adjust padding depending on line count and font size.
             contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext,
-                    hasThreeLines(), mContext.getResources().getConfiguration().fontScale),
+                    mHasThreeLines, mContext.getResources().getConfiguration().fontScale),
                     0, 0);
 
             // We want to add badge to first line of text.
@@ -2721,7 +2739,12 @@
          *         is going to have one or two lines
          */
         private boolean hasThreeLines() {
-            boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0;
+            boolean contentTextInLine2 = mSubText != null && mContentText != null;
+
+            // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
+            boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
+            boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0
+                    || badgeInLine3;
             boolean hasLine2 = (mSubText != null && mContentText != null) ||
                     (mSubText == null && (mProgressMax != 0 || mProgressIndeterminate));
             return hasLine2 && hasLine3;
@@ -3092,6 +3115,7 @@
             if (mRebuildNotification == null) {
                 throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode.");
             }
+            mHasThreeLines = hasThreeLines();
 
             Bundle extras = mRebuildNotification.extras;
 
@@ -3124,6 +3148,7 @@
             }
             extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW);
 
+            mHasThreeLines = false;
             return mRebuildNotification;
         }
 
@@ -3238,6 +3263,7 @@
          */
         public Notification build() {
             mOriginatingUserId = mContext.getUserId();
+            mHasThreeLines = hasThreeLines();
 
             Notification n = buildUnstyled();
 
@@ -3259,6 +3285,7 @@
                 mStyle.addExtras(n.extras);
             }
 
+            mHasThreeLines = false;
             return n;
         }
 
@@ -3388,7 +3415,7 @@
          */
         protected void applyTopPadding(RemoteViews contentView) {
             int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
-                    mBuilder.hasThreeLines(),
+                    mBuilder.mHasThreeLines,
                     mBuilder.mContext.getResources().getConfiguration().fontScale);
             contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
         }
@@ -3661,6 +3688,8 @@
 
             applyTopPadding(contentView);
 
+            mBuilder.shrinkLine3Text(contentView);
+
             mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
 
             return contentView;
@@ -3800,6 +3829,8 @@
 
             applyTopPadding(contentView);
 
+            mBuilder.shrinkLine3Text(contentView);
+
             mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
 
             return contentView;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fc047de..7dc1ad64 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -20,6 +20,7 @@
 import android.app.Notification.Builder;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -251,5 +252,17 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public boolean matchesCallFilter(Bundle extras) {
+        INotificationManager service = getService();
+        try {
+            return service.matchesCallFilter(extras);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     private Context mContext;
 }
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 2431ad0..fb80de2 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -106,7 +106,9 @@
         }
 
         /**
-         * The time at which this event occurred.
+         * The time at which this event occurred, measured in milliseconds since the epoch.
+         * <p/>
+         * See {@link System#currentTimeMillis()}.
          */
         public long getTimeStamp() {
             return mTimeStamp;
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index e47a802..abfc435 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -81,28 +81,36 @@
     }
 
     /**
-     * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents.
+     * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents,
+     * measured in milliseconds since the epoch.
+     * <p/>
+     * See {@link System#currentTimeMillis()}.
      */
     public long getFirstTimeStamp() {
         return mBeginTimeStamp;
     }
 
     /**
-     * Get the end of the time range this {@link android.app.usage.UsageStats} represents.
+     * Get the end of the time range this {@link android.app.usage.UsageStats} represents,
+     * measured in milliseconds since the epoch.
+     * <p/>
+     * See {@link System#currentTimeMillis()}.
      */
     public long getLastTimeStamp() {
         return mEndTimeStamp;
     }
 
     /**
-     * Get the last time this package was used.
+     * Get the last time this package was used, measured in milliseconds since the epoch.
+     * <p/>
+     * See {@link System#currentTimeMillis()}.
      */
     public long getLastTimeUsed() {
         return mLastTimeUsed;
     }
 
     /**
-     * Get the total time this package spent in the foreground.
+     * Get the total time this package spent in the foreground, measured in milliseconds.
      */
     public long getTotalTimeInForeground() {
         return mTotalTimeInForeground;
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index f9b8928..5830fcf 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -23,6 +23,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Provides access to device usage history and statistics. Usage data is aggregated into
@@ -149,7 +150,6 @@
      * @param endTime The exclusive end of the range of events to include in the results.
      * @return A {@link UsageEvents}.
      */
-    @SuppressWarnings("unchecked")
     public UsageEvents queryEvents(long beginTime, long endTime) {
         try {
             UsageEvents iter = mService.queryEvents(beginTime, endTime,
@@ -170,15 +170,13 @@
      *
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
      * @param endTime The exclusive end of the range of stats to include in the results.
-     * @return An {@link android.util.ArrayMap} keyed by package name or null if no stats are
+     * @return A {@link java.util.Map} keyed by package name, or null if no stats are
      *         available.
      */
-    public ArrayMap<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
+    public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
         List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
         if (stats.isEmpty()) {
-            @SuppressWarnings("unchecked")
-            ArrayMap<String, UsageStats> emptyStats = ArrayMap.EMPTY;
-            return emptyStats;
+            return Collections.emptyMap();
         }
 
         ArrayMap<String, UsageStats> aggregatedStats = new ArrayMap<>();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b7d7c25..f979a0c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1486,8 +1486,6 @@
      * @see #sendBroadcast(Intent)
      * @see #sendBroadcast(Intent, String)
      * @see #sendOrderedBroadcast(Intent, String)
-     * @see #sendStickyBroadcast(Intent)
-     * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
      * @see android.content.BroadcastReceiver
      * @see #registerReceiver
      * @see android.app.Activity#RESULT_OK
@@ -1584,7 +1582,7 @@
             @Nullable  Bundle initialExtras);
 
     /**
-     * Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
+     * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
      * Intent you are sending stays around after the broadcast is complete,
      * so that others can quickly retrieve that data through the return
      * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}.  In
@@ -1595,6 +1593,12 @@
      * permission in order to use this API.  If you do not hold that
      * permission, {@link SecurityException} will be thrown.
      *
+     * @deprecated Sticky broadcasts should not be used.  They provide no security (anyone
+     * can access them), no protection (anyone can modify them), and many other problems.
+     * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+     * has changed, with another mechanism for apps to retrieve the current value whenever
+     * desired.
+     *
      * @param intent The Intent to broadcast; all receivers matching this
      * Intent will receive the broadcast, and the Intent will be held to
      * be re-broadcast to future receivers.
@@ -1602,10 +1606,11 @@
      * @see #sendBroadcast(Intent)
      * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
      */
+    @Deprecated
     public abstract void sendStickyBroadcast(Intent intent);
 
     /**
-     * Version of {@link #sendStickyBroadcast} that allows you to
+     * <p>Version of {@link #sendStickyBroadcast} that allows you to
      * receive data back from the broadcast.  This is accomplished by
      * supplying your own BroadcastReceiver when calling, which will be
      * treated as a final receiver at the end of the broadcast -- its
@@ -1622,6 +1627,12 @@
      *
      * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
      *
+     * @deprecated Sticky broadcasts should not be used.  They provide no security (anyone
+     * can access them), no protection (anyone can modify them), and many other problems.
+     * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+     * has changed, with another mechanism for apps to retrieve the current value whenever
+     * desired.
+     *
      * @param intent The Intent to broadcast; all receivers matching this
      *               Intent will receive the broadcast.
      * @param resultReceiver Your own BroadcastReceiver to treat as the final
@@ -1644,31 +1655,45 @@
      * @see #registerReceiver
      * @see android.app.Activity#RESULT_OK
      */
+    @Deprecated
     public abstract void sendStickyOrderedBroadcast(Intent intent,
             BroadcastReceiver resultReceiver,
             @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
             @Nullable Bundle initialExtras);
 
     /**
-     * Remove the data previously sent with {@link #sendStickyBroadcast},
+     * <p>Remove the data previously sent with {@link #sendStickyBroadcast},
      * so that it is as if the sticky broadcast had never happened.
      *
      * <p>You must hold the {@link android.Manifest.permission#BROADCAST_STICKY}
      * permission in order to use this API.  If you do not hold that
      * permission, {@link SecurityException} will be thrown.
      *
+     * @deprecated Sticky broadcasts should not be used.  They provide no security (anyone
+     * can access them), no protection (anyone can modify them), and many other problems.
+     * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+     * has changed, with another mechanism for apps to retrieve the current value whenever
+     * desired.
+     *
      * @param intent The Intent that was previously broadcast.
      *
      * @see #sendStickyBroadcast
      */
+    @Deprecated
     public abstract void removeStickyBroadcast(Intent intent);
 
     /**
-     * Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the
+     * <p>Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the
      * user the broadcast will be sent to.  This is not available to applications
      * that are not pre-installed on the system image.  Using it requires holding
      * the INTERACT_ACROSS_USERS permission.
      *
+     * @deprecated Sticky broadcasts should not be used.  They provide no security (anyone
+     * can access them), no protection (anyone can modify them), and many other problems.
+     * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+     * has changed, with another mechanism for apps to retrieve the current value whenever
+     * desired.
+     *
      * @param intent The Intent to broadcast; all receivers matching this
      * Intent will receive the broadcast, and the Intent will be held to
      * be re-broadcast to future receivers.
@@ -1676,10 +1701,11 @@
      *
      * @see #sendBroadcast(Intent)
      */
+    @Deprecated
     public abstract void sendStickyBroadcastAsUser(Intent intent, UserHandle user);
 
     /**
-     * Version of
+     * <p>Version of
      * {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)}
      * that allows you to specify the
      * user the broadcast will be sent to.  This is not available to applications
@@ -1688,6 +1714,12 @@
      *
      * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
      *
+     * @deprecated Sticky broadcasts should not be used.  They provide no security (anyone
+     * can access them), no protection (anyone can modify them), and many other problems.
+     * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+     * has changed, with another mechanism for apps to retrieve the current value whenever
+     * desired.
+     *
      * @param intent The Intent to broadcast; all receivers matching this
      *               Intent will receive the broadcast.
      * @param user UserHandle to send the intent to.
@@ -1705,13 +1737,14 @@
      *
      * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
      */
+    @Deprecated
     public abstract void sendStickyOrderedBroadcastAsUser(Intent intent,
             UserHandle user, BroadcastReceiver resultReceiver,
             @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
             @Nullable Bundle initialExtras);
 
     /**
-     * Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the
+     * <p>Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the
      * user the broadcast will be sent to.  This is not available to applications
      * that are not pre-installed on the system image.  Using it requires holding
      * the INTERACT_ACROSS_USERS permission.
@@ -1720,11 +1753,18 @@
      * permission in order to use this API.  If you do not hold that
      * permission, {@link SecurityException} will be thrown.
      *
+     * @deprecated Sticky broadcasts should not be used.  They provide no security (anyone
+     * can access them), no protection (anyone can modify them), and many other problems.
+     * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+     * has changed, with another mechanism for apps to retrieve the current value whenever
+     * desired.
+     *
      * @param intent The Intent that was previously broadcast.
      * @param user UserHandle to remove the sticky broadcast from.
      *
      * @see #sendStickyBroadcastAsUser
      */
+    @Deprecated
     public abstract void removeStickyBroadcastAsUser(Intent intent, UserHandle user);
 
     /**
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 44478d4..bd1487c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -198,6 +198,14 @@
             in VerificationParams verificationParams,
             in String packageAbiOverride);
 
+    void installPackageAsUser(in String originPath,
+            in IPackageInstallObserver2 observer,
+            int flags,
+            in String installerPackageName,
+            in VerificationParams verificationParams,
+            in String packageAbiOverride,
+            int userId);
+
     void finishPackageInstall(int token);
 
     void setInstallerPackageName(in String targetPackage, in String installerPackageName);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c928a18..44e24b1 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -623,7 +623,7 @@
             try {
                 final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
                         offsetBytes, lengthBytes);
-                return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor());
+                return new FileBridge.FileBridgeOutputStream(clientSocket);
             } catch (RuntimeException e) {
                 ExceptionUtils.maybeUnwrapIOException(e);
                 throw e;
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index ec4bc7d..0895fe3 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -19,6 +19,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -48,13 +49,23 @@
  */
 public final class TotalCaptureResult extends CaptureResult {
 
+    private final List<CaptureResult> mPartialResults;
+
     /**
-     * Takes ownership of the passed-in properties object
+     * Takes ownership of the passed-in camera metadata and the partial results
+     *
+     * @param partials a list of partial results; {@code null} will be substituted for an empty list
      * @hide
      */
     public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
-            CaptureResultExtras extras) {
+            CaptureResultExtras extras, List<CaptureResult> partials) {
         super(results, parent, extras);
+
+        if (partials == null) {
+            mPartialResults = new ArrayList<>();
+        } else {
+            mPartialResults = partials;
+        }
     }
 
     /**
@@ -65,6 +76,8 @@
      */
     public TotalCaptureResult(CameraMetadataNative results, int sequenceId) {
         super(results, sequenceId);
+
+        mPartialResults = new ArrayList<>();
     }
 
     /**
@@ -73,14 +86,13 @@
      * <p>The list is returned is unmodifiable; attempting to modify it will result in a
      * {@code UnsupportedOperationException} being thrown.</p>
      *
-     * <p>The list size will be inclusive between {@code 1} and
-     * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, in ascending order
+     * <p>The list size will be inclusive between {@code 0} and
+     * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, with elements in ascending order
      * of when {@link CameraCaptureSession.CaptureListener#onCaptureProgressed} was invoked.</p>
      *
      * @return unmodifiable list of partial results
      */
     public List<CaptureResult> getPartialResults() {
-        // TODO
-        return Collections.unmodifiableList(null);
+        return Collections.unmodifiableList(mPartialResults);
     }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d75dfe6..f5666bf 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -41,6 +41,7 @@
 
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -967,6 +968,8 @@
 
         private long mCompletedFrameNumber = -1;
         private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>();
+        /** Map frame numbers to list of partial results */
+        private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>();
 
         private void update() {
             Iterator<Long> iter = mFutureErrorSet.iterator();
@@ -983,8 +986,8 @@
 
         /**
          * This function is called every time when a result or an error is received.
-         * @param frameNumber: the frame number corresponding to the result or error
-         * @param isError: true if it is an error, false if it is not an error
+         * @param frameNumber the frame number corresponding to the result or error
+         * @param isError true if it is an error, false if it is not an error
          */
         public void updateTracker(long frameNumber, boolean isError) {
             if (isError) {
@@ -1006,6 +1009,55 @@
             update();
         }
 
+        /**
+         * This function is called every time a result has been completed.
+         *
+         * <p>It keeps a track of all the partial results already created for a particular
+         * frame number.</p>
+         *
+         * @param frameNumber the frame number corresponding to the result
+         * @param result the total or partial result
+         * @param partial {@true} if the result is partial, {@code false} if total
+         */
+        public void updateTracker(long frameNumber, CaptureResult result, boolean partial) {
+
+            if (!partial) {
+                // Update the total result's frame status as being successful
+                updateTracker(frameNumber, /*isError*/false);
+                // Don't keep a list of total results, we don't need to track them
+                return;
+            }
+
+            if (result == null) {
+                // Do not record blank results; this also means there will be no total result
+                // so it doesn't matter that the partials were not recorded
+                return;
+            }
+
+            // Partial results must be aggregated in-order for that frame number
+            List<CaptureResult> partials = mPartialResults.get(frameNumber);
+            if (partials == null) {
+                partials = new ArrayList<>();
+                mPartialResults.put(frameNumber, partials);
+            }
+
+            partials.add(result);
+        }
+
+        /**
+         * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}.
+         *
+         * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker}
+         * is called again with new partials for that frame number).</p>
+         *
+         * @param frameNumber the frame number corresponding to the result
+         * @return a list of partial results for that frame with at least 1 element,
+         *         or {@code null} if there were no partials recorded for that frame
+         */
+        public List<CaptureResult> popPartialResults(long frameNumber) {
+            return mPartialResults.remove(frameNumber);
+        }
+
         public long getCompletedFrameNumber() {
             return mCompletedFrameNumber;
         }
@@ -1244,12 +1296,6 @@
                 boolean isPartialResult =
                         (resultExtras.getPartialResultCount() < mTotalPartialCount);
 
-                // Update tracker (increment counter) when it's not a partial result.
-                if (!isPartialResult) {
-                    mFrameNumberTracker.updateTracker(frameNumber,
-                            /*error*/false);
-                }
-
                 // Check if we have a listener for this
                 if (holder == null) {
                     if (DEBUG) {
@@ -1257,6 +1303,9 @@
                                 "holder is null, early return at frame "
                                         + frameNumber);
                     }
+
+                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult);
+
                     return;
                 }
 
@@ -1266,6 +1315,8 @@
                                 "camera is closed, early return at frame "
                                         + frameNumber);
                     }
+
+                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult);
                     return;
                 }
 
@@ -1273,6 +1324,8 @@
 
                 Runnable resultDispatch = null;
 
+                CaptureResult finalResult;
+
                 // Either send a partial result or the final capture completed result
                 if (isPartialResult) {
                     final CaptureResult resultAsCapture =
@@ -1290,9 +1343,14 @@
                             }
                         }
                     };
+
+                    finalResult = resultAsCapture;
                 } else {
+                    List<CaptureResult> partialResults =
+                            mFrameNumberTracker.popPartialResults(frameNumber);
+
                     final TotalCaptureResult resultAsCapture =
-                            new TotalCaptureResult(result, request, resultExtras);
+                            new TotalCaptureResult(result, request, resultExtras, partialResults);
 
                     // Final capture result
                     resultDispatch = new Runnable() {
@@ -1306,10 +1364,15 @@
                             }
                         }
                     };
+
+                    finalResult = resultAsCapture;
                 }
 
                 holder.getHandler().post(resultDispatch);
 
+                // Collect the partials for a total result; or mark the frame as totally completed
+                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult);
+
                 // Fire onCaptureSequenceCompleted
                 if (!isPartialResult) {
                     checkAndFireSequenceComplete();
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index febb015..f47ce79 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -67,7 +67,6 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 
 /**
  * Implementation of camera metadata marshal/unmarshal across Binder to
@@ -655,6 +654,15 @@
 
     private Face[] getFaces() {
         Integer faceDetectMode = get(CaptureResult.STATISTICS_FACE_DETECT_MODE);
+        byte[] faceScores = get(CaptureResult.STATISTICS_FACE_SCORES);
+        Rect[] faceRectangles = get(CaptureResult.STATISTICS_FACE_RECTANGLES);
+        int[] faceIds = get(CaptureResult.STATISTICS_FACE_IDS);
+        int[] faceLandmarks = get(CaptureResult.STATISTICS_FACE_LANDMARKS);
+
+        if (areValuesAllNull(faceDetectMode, faceScores, faceRectangles, faceIds, faceLandmarks)) {
+            return null;
+        }
+
         if (faceDetectMode == null) {
             Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE");
             faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE;
@@ -670,8 +678,6 @@
         }
 
         // Face scores and rectangles are required by SIMPLE and FULL mode.
-        byte[] faceScores = get(CaptureResult.STATISTICS_FACE_SCORES);
-        Rect[] faceRectangles = get(CaptureResult.STATISTICS_FACE_RECTANGLES);
         if (faceScores == null || faceRectangles == null) {
             Log.w(TAG, "Expect face scores and rectangles to be non-null");
             return new Face[0];
@@ -683,8 +689,6 @@
         // To be safe, make number of faces is the minimal of all face info metadata length.
         int numFaces = Math.min(faceScores.length, faceRectangles.length);
         // Face id and landmarks are only required by FULL mode.
-        int[] faceIds = get(CaptureResult.STATISTICS_FACE_IDS);
-        int[] faceLandmarks = get(CaptureResult.STATISTICS_FACE_LANDMARKS);
         if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
             if (faceIds == null || faceLandmarks == null) {
                 Log.w(TAG, "Expect face ids and landmarks to be non-null for FULL mode," +
@@ -755,22 +759,32 @@
 
     private LensShadingMap getLensShadingMap() {
         float[] lsmArray = getBase(CaptureResult.STATISTICS_LENS_SHADING_MAP);
+        Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE);
+
+        // Do not warn if lsmArray is null while s is not. This is valid.
         if (lsmArray == null) {
-            Log.w(TAG, "getLensShadingMap - Lens shading map was null.");
             return null;
         }
-        Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE);
+
+        if (s == null) {
+            Log.w(TAG, "getLensShadingMap - Lens shading map size was null.");
+            return null;
+        }
+
         LensShadingMap map = new LensShadingMap(lsmArray, s.getHeight(), s.getWidth());
         return map;
     }
 
     private Location getGpsLocation() {
         String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
-        Location l = new Location(translateProcessToLocationProvider(processingMethod));
-
         double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES);
         Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP);
 
+        if (areValuesAllNull(processingMethod, coords, timeStamp)) {
+            return null;
+        }
+
+        Location l = new Location(translateProcessToLocationProvider(processingMethod));
         if (timeStamp != null) {
             l.setTime(timeStamp);
         } else {
@@ -873,7 +887,13 @@
         float[] red = getBase(CaptureRequest.TONEMAP_CURVE_RED);
         float[] green = getBase(CaptureRequest.TONEMAP_CURVE_GREEN);
         float[] blue = getBase(CaptureRequest.TONEMAP_CURVE_BLUE);
+
+        if (areValuesAllNull(red, green, blue)) {
+            return null;
+        }
+
         if (red == null || green == null || blue == null) {
+            Log.w(TAG, "getTonemapCurve - missing tone curve components");
             return null;
         }
         TonemapCurve tc = new TonemapCurve(red, green, blue);
@@ -1208,6 +1228,18 @@
         }
     }
 
+    /** Check if input arguments are all {@code null}.
+     *
+     * @param objs Input arguments for null check
+     * @return {@code true} if input arguments are all {@code null}, otherwise {@code false}
+     */
+    private static boolean areValuesAllNull(Object... objs) {
+        for (Object o : objs) {
+            if (o != null) return false;
+        }
+        return true;
+    }
+
     static {
         /*
          * We use a class initializer to allow the native code to cache some field offsets
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index 85e7531..2ec6126 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -166,7 +166,7 @@
     public String toString() {
         StringBuffer s = new StringBuffer();
         s.append("port_id: ").append(mId).append(", ");
-        s.append("address: ").append(mAddress).append(", ");
+        s.append("address: ").append(String.format("0x%04x", mAddress)).append(", ");
         s.append("cec: ").append(mCecSupported).append(", ");
         s.append("arc: ").append(mArcSupported).append(", ");
         s.append("mhl: ").append(mMhlSupported);
diff --git a/core/java/android/net/NetworkBoundURLFactory.java b/core/java/android/net/NetworkBoundURLFactory.java
deleted file mode 100644
index 356100e..0000000
--- a/core/java/android/net/NetworkBoundURLFactory.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * An interface that describes a factory for network-specific {@link URL} objects.
- */
-public interface NetworkBoundURLFactory {
-    /**
-     * Returns a {@link URL} based on the given URL but bound to the specified {@code Network},
-     * such that opening the URL will send all network traffic on the specified Network.
-     *
-     * @return a {@link URL} bound to this {@code Network}.
-     * @throws MalformedURLException if the URL was not valid, or this factory cannot handle the
-     *         specified URL (e.g., if it does not support the protocol of the URL).
-     */
-    public URL getBoundURL(Network network, URL url) throws MalformedURLException;
-}
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
index bf8d15c..022a106 100644
--- a/core/java/android/os/FileBridge.java
+++ b/core/java/android/os/FileBridge.java
@@ -131,10 +131,17 @@
     }
 
     public static class FileBridgeOutputStream extends OutputStream {
+        private final ParcelFileDescriptor mClientPfd;
         private final FileDescriptor mClient;
         private final byte[] mTemp = new byte[MSG_LENGTH];
 
+        public FileBridgeOutputStream(ParcelFileDescriptor clientPfd) {
+            mClientPfd = clientPfd;
+            mClient = clientPfd.getFileDescriptor();
+        }
+
         public FileBridgeOutputStream(FileDescriptor client) {
+            mClientPfd = null;
             mClient = client;
         }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ae11f47..33e0468 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -904,6 +904,15 @@
     public static final String ACTION_APP_NOTIFICATION_SETTINGS
             = "android.settings.APP_NOTIFICATION_SETTINGS";
 
+    /**
+     * Activity Action: Show notification redaction settings.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_APP_NOTIFICATION_REDACTION
+            = "android.settings.ACTION_APP_NOTIFICATION_REDACTION";
+
     /** @hide */ public static final String EXTRA_APP_UID = "app_uid";
     /** @hide */ public static final String EXTRA_APP_PACKAGE = "app_package";
 
@@ -3714,6 +3723,14 @@
                 "lock_screen_allow_private_notifications";
 
         /**
+         * Set by the system to track if the user needs to see the call to action for
+         * the lockscreen notification policy.
+         * @hide
+         */
+        public static final String SHOW_NOTE_ABOUT_NOTIFICATION_HIDING =
+                "show_note_about_notification_hiding";
+
+        /**
          * The Logging ID (a unique 64-bit value) as a hex string.
          * Used as a pseudonymous identifier for logging.
          * @deprecated This identifier is poorly initialized and has
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index a544b2d..cb0bcf2 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -81,6 +81,33 @@
      * This does not change the interruption filter, only the effects. **/
     public static final int HINT_HOST_DISABLE_EFFECTS = 1;
 
+    /**
+     * The full trim of the StatusBarNotification including all its features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int TRIM_FULL = 0;
+
+    /**
+     * A light trim of the StatusBarNotification excluding the following features:
+     *
+     * <ol>
+     *     <li>{@link Notification#tickerView tickerView}</li>
+     *     <li>{@link Notification#contentView contentView}</li>
+     *     <li>{@link Notification#largeIcon largeIcon}</li>
+     *     <li>{@link Notification#bigContentView bigContentView}</li>
+     *     <li>{@link Notification#headsUpContentView headsUpContentView}</li>
+     *     <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
+     *     <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
+     *     <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
+     * </ol>
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int TRIM_LIGHT = 1;
+
     private INotificationListenerWrapper mWrapper = null;
     private RankingMap mRankingMap;
 
@@ -314,13 +341,53 @@
     }
 
     /**
+     * Sets the notification trim that will be received via {@link #onNotificationPosted}.
+     *
+     * <p>
+     * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
+     * full notification features right away to reduce their memory footprint. Full notifications
+     * can be requested on-demand via {@link #getActiveNotifications(int)}.
+     *
+     * <p>
+     * Set to {@link #TRIM_FULL} initially.
+     *
+     * @hide
+     *
+     * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
+     *             See <code>TRIM_*</code> constants.
+     */
+    @SystemApi
+    public final void setOnNotificationPostedTrim(int trim) {
+        if (!isBound()) return;
+        try {
+            getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
+        } catch (RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    /**
      * Request the list of outstanding notifications (that is, those that are visible to the
      * current user). Useful when you don't know what's already been posted.
      *
      * @return An array of active notifications, sorted in natural order.
      */
     public StatusBarNotification[] getActiveNotifications() {
-        return getActiveNotifications(null);
+        return getActiveNotifications(null, TRIM_FULL);
+    }
+
+    /**
+     * Request the list of outstanding notifications (that is, those that are visible to the
+     * current user). Useful when you don't know what's already been posted.
+     *
+     * @hide
+     *
+     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
+     * @return An array of active notifications, sorted in natural order.
+     */
+    @SystemApi
+    public StatusBarNotification[] getActiveNotifications(int trim) {
+        return getActiveNotifications(null, trim);
     }
 
     /**
@@ -328,14 +395,33 @@
      * notifications but didn't want to retain the bits, and now need to go back and extract
      * more data out of those notifications.
      *
+     * @param keys the keys of the notifications to request
      * @return An array of notifications corresponding to the requested keys, in the
      * same order as the key list.
      */
     public StatusBarNotification[] getActiveNotifications(String[] keys) {
-        if (!isBound()) return null;
+        return getActiveNotifications(keys, TRIM_FULL);
+    }
+
+    /**
+     * Request one or more notifications by key. Useful if you have been keeping track of
+     * notifications but didn't want to retain the bits, and now need to go back and extract
+     * more data out of those notifications.
+     *
+     * @hide
+     *
+     * @param keys the keys of the notifications to request
+     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
+     * @return An array of notifications corresponding to the requested keys, in the
+     * same order as the key list.
+     */
+    @SystemApi
+    public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
+        if (!isBound())
+            return null;
         try {
-            ParceledListSlice<StatusBarNotification> parceledList =
-                    getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys);
+            ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
+                    .getActiveNotificationsFromListener(mWrapper, keys, trim);
             List<StatusBarNotification> list = parceledList.getList();
 
             int N = list.size();
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 2095773..519bc28 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Activity;
 import android.content.Intent;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
@@ -84,20 +85,31 @@
     private static final int STATE_NOT_READY = 0;
 
     // Keyphrase management actions. Used in getManageIntent() ----//
-    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
                 MANAGE_ACTION_ENROLL,
                 MANAGE_ACTION_RE_ENROLL,
                 MANAGE_ACTION_UN_ENROLL
             })
-    public @interface ManageActions {}
+    private @interface ManageActions {}
 
-    /** Indicates that we need to enroll. */
+    /**
+     * Indicates that we need to enroll.
+     *
+     * @hide
+     */
     public static final int MANAGE_ACTION_ENROLL = 0;
-    /** Indicates that we need to re-enroll. */
+    /**
+     * Indicates that we need to re-enroll.
+     *
+     * @hide
+     */
     public static final int MANAGE_ACTION_RE_ENROLL = 1;
-    /** Indicates that we need to un-enroll. */
+    /**
+     * Indicates that we need to un-enroll.
+     *
+     * @hide
+     */
     public static final int MANAGE_ACTION_UN_ENROLL = 2;
 
     //-- Flags for startRecognition    ----//
@@ -111,7 +123,11 @@
             })
     public @interface RecognitionFlags {}
 
-    /** Empty flag for {@link #startRecognition(int)}. */
+    /**
+     * Empty flag for {@link #startRecognition(int)}.
+     *
+     * @hide
+     */
     public static final int RECOGNITION_FLAG_NONE = 0;
     /**
      * Recognition flag for {@link #startRecognition(int)} that indicates
@@ -264,7 +280,7 @@
     /**
      * Callbacks for always-on hotword detection.
      */
-    public interface Callback {
+    public static abstract class Callback {
         /**
          * Called when the hotword availability changes.
          * This indicates a change in the availability of recognition for the given keyphrase.
@@ -278,7 +294,7 @@
          * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_UNENROLLED
          * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_ENROLLED
          */
-        void onAvailabilityChanged(int status);
+        public abstract void onAvailabilityChanged(int status);
         /**
          * Called when the keyphrase is spoken.
          * This implicitly stops listening for the keyphrase once it's detected.
@@ -289,23 +305,23 @@
          *        This may contain the trigger audio, if requested when calling
          *        {@link AlwaysOnHotwordDetector#startRecognition(int)}.
          */
-        void onDetected(@NonNull EventPayload eventPayload);
+        public abstract void onDetected(@NonNull EventPayload eventPayload);
         /**
          * Called when the detection fails due to an error.
          */
-        void onError();
+        public abstract void onError();
         /**
          * Called when the recognition is paused temporarily for some reason.
          * This is an informational callback, and the clients shouldn't be doing anything here
          * except showing an indication on their UI if they have to.
          */
-        void onRecognitionPaused();
+        public abstract void onRecognitionPaused();
         /**
          * Called when the recognition is resumed after it was temporarily paused.
          * This is an informational callback, and the clients shouldn't be doing anything here
          * except showing an indication on their UI if they have to.
          */
-        void onRecognitionResumed();
+        public abstract void onRecognitionResumed();
     }
 
     /**
@@ -372,10 +388,10 @@
     /**
      * Starts recognition for the associated keyphrase.
      *
+     * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+     * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+     *
      * @param recognitionFlags The flags to control the recognition properties.
-     *        The allowed flags are {@link #RECOGNITION_FLAG_NONE},
-     *        {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO} and
-     *        {@link #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS}.
      * @return Indicates whether the call succeeded or not.
      * @throws UnsupportedOperationException if the recognition isn't supported.
      *         Callers should only call this method after a supported state callback on
@@ -430,12 +446,13 @@
     }
 
     /**
-     * Gets an intent to manage the associated keyphrase.
+     * Creates an intent to start the enrollment for the associated keyphrase.
+     * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}.
+     * Starting re-enrollment is only valid if the keyphrase is un-enrolled,
+     * i.e. {@link #STATE_KEYPHRASE_UNENROLLED},
+     * otherwise {@link #createIntentToReEnroll()} should be preferred.
      *
-     * @param action The manage action that needs to be performed.
-     *        One of {@link #MANAGE_ACTION_ENROLL}, {@link #MANAGE_ACTION_RE_ENROLL} or
-     *        {@link #MANAGE_ACTION_UN_ENROLL}.
-     * @return An {@link Intent} to manage the given keyphrase.
+     * @return An {@link Intent} to start enrollment for the given keyphrase.
      * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
      *         Callers should only call this method after a supported state callback on
      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
@@ -443,10 +460,52 @@
      *         This may happen if another detector has been instantiated or the
      *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
-    public Intent getManageIntent(@ManageActions int action) {
-        if (DBG) Slog.d(TAG, "getManageIntent(" + action + ")");
+    public Intent createIntentToEnroll() {
+        if (DBG) Slog.d(TAG, "createIntentToEnroll");
         synchronized (mLock) {
-            return getManageIntentLocked(action);
+            return getManageIntentLocked(MANAGE_ACTION_ENROLL);
+        }
+    }
+
+    /**
+     * Creates an intent to start the un-enrollment for the associated keyphrase.
+     * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}.
+     * Starting re-enrollment is only valid if the keyphrase is already enrolled,
+     * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
+     *
+     * @return An {@link Intent} to start un-enrollment for the given keyphrase.
+     * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
+     */
+    public Intent createIntentToUnEnroll() {
+        if (DBG) Slog.d(TAG, "createIntentToUnEnroll");
+        synchronized (mLock) {
+            return getManageIntentLocked(MANAGE_ACTION_UN_ENROLL);
+        }
+    }
+
+    /**
+     * Creates an intent to start the re-enrollment for the associated keyphrase.
+     * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}.
+     * Starting re-enrollment is only valid if the keyphrase is already enrolled,
+     * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
+     *
+     * @return An {@link Intent} to start re-enrollment for the given keyphrase.
+     * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
+     */
+    public Intent createIntentToReEnroll() {
+        if (DBG) Slog.d(TAG, "createIntentToReEnroll");
+        synchronized (mLock) {
+            return getManageIntentLocked(MANAGE_ACTION_RE_ENROLL);
         }
     }
 
@@ -462,12 +521,6 @@
                     "Managing the given keyphrase is not supported");
         }
 
-        if (action != MANAGE_ACTION_ENROLL
-                && action != MANAGE_ACTION_RE_ENROLL
-                && action != MANAGE_ACTION_UN_ENROLL) {
-            throw new IllegalArgumentException("Invalid action specified " + action);
-        }
-
         return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale);
     }
 
diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java
index ec79b36..3a63805 100644
--- a/core/java/android/text/format/TimeFormatter.java
+++ b/core/java/android/text/format/TimeFormatter.java
@@ -22,8 +22,7 @@
 
 import android.content.res.Resources;
 
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
+import java.nio.CharBuffer;
 import java.util.Formatter;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -31,15 +30,13 @@
 import libcore.util.ZoneInfo;
 
 /**
- * Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java. The
- * main issue with this implementation is the treatment of characters as ASCII, despite returning
- * localized (UTF-16) strings from the LocaleData.
+ * Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java.
  *
  * <p>This class is not thread safe.
  */
 class TimeFormatter {
-    // An arbitrary value outside the range representable by a byte / ASCII character code.
-    private static final int FORCE_LOWER_CASE = 0x100;
+    // An arbitrary value outside the range representable by a char.
+    private static final int FORCE_LOWER_CASE = -1;
 
     private static final int SECSPERMIN = 60;
     private static final int MINSPERHOUR = 60;
@@ -62,10 +59,9 @@
     private final String dateTimeFormat;
     private final String timeOnlyFormat;
     private final String dateOnlyFormat;
-    private final Locale locale;
 
     private StringBuilder outputBuilder;
-    private Formatter outputFormatter;
+    private Formatter numberFormatter;
 
     public TimeFormatter() {
         synchronized (TimeFormatter.class) {
@@ -84,7 +80,6 @@
             this.dateTimeFormat = sDateTimeFormat;
             this.timeOnlyFormat = sTimeOnlyFormat;
             this.dateOnlyFormat = sDateOnlyFormat;
-            this.locale = locale;
             localeData = sLocaleData;
         }
     }
@@ -97,19 +92,21 @@
             StringBuilder stringBuilder = new StringBuilder();
 
             outputBuilder = stringBuilder;
-            outputFormatter = new Formatter(stringBuilder, locale);
+            // This uses the US locale because number localization is handled separately (see below)
+            // and locale sensitive strings are output directly using outputBuilder.
+            numberFormatter = new Formatter(stringBuilder, Locale.US);
 
             formatInternal(pattern, wallTime, zoneInfo);
             String result = stringBuilder.toString();
             // This behavior is the source of a bug since some formats are defined as being
-            // in ASCII. Generally localization is very broken.
+            // in ASCII and not localized.
             if (localeData.zeroDigit != '0') {
                 result = localizeDigits(result);
             }
             return result;
         } finally {
             outputBuilder = null;
-            outputFormatter = null;
+            numberFormatter = null;
         }
     }
 
@@ -132,38 +129,30 @@
      * {@link #outputBuilder}.
      */
     private void formatInternal(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) {
-        // Convert to ASCII bytes to be compatible with old implementation behavior.
-        byte[] bytes = pattern.getBytes(StandardCharsets.US_ASCII);
-        if (bytes.length == 0) {
-            return;
-        }
-
-        ByteBuffer formatBuffer = ByteBuffer.wrap(bytes);
+        CharBuffer formatBuffer = CharBuffer.wrap(pattern);
         while (formatBuffer.remaining() > 0) {
-            boolean outputCurrentByte = true;
-            char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position()));
-            if (currentByteAsChar == '%') {
-                outputCurrentByte = handleToken(formatBuffer, wallTime, zoneInfo);
+            boolean outputCurrentChar = true;
+            char currentChar = formatBuffer.get(formatBuffer.position());
+            if (currentChar == '%') {
+                outputCurrentChar = handleToken(formatBuffer, wallTime, zoneInfo);
             }
-            if (outputCurrentByte) {
-                currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position()));
-                outputBuilder.append(currentByteAsChar);
+            if (outputCurrentChar) {
+                outputBuilder.append(formatBuffer.get(formatBuffer.position()));
             }
-
             formatBuffer.position(formatBuffer.position() + 1);
         }
     }
 
-    private boolean handleToken(ByteBuffer formatBuffer, ZoneInfo.WallTime wallTime,
+    private boolean handleToken(CharBuffer formatBuffer, ZoneInfo.WallTime wallTime,
             ZoneInfo zoneInfo) {
 
-        // The byte at formatBuffer.position() is expected to be '%' at this point.
+        // The char at formatBuffer.position() is expected to be '%' at this point.
         int modifier = 0;
         while (formatBuffer.remaining() > 1) {
-            // Increment the position then get the new current byte.
+            // Increment the position then get the new current char.
             formatBuffer.position(formatBuffer.position() + 1);
-            char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position()));
-            switch (currentByteAsChar) {
+            char currentChar = formatBuffer.get(formatBuffer.position());
+            switch (currentChar) {
                 case 'A':
                     modifyAndAppend((wallTime.getWeekDay() < 0
                                     || wallTime.getWeekDay() >= DAYSPERWEEK)
@@ -206,7 +195,7 @@
                     formatInternal("%m/%d/%y", wallTime, zoneInfo);
                     return false;
                 case 'd':
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
                             wallTime.getMonthDay());
                     return false;
                 case 'E':
@@ -218,46 +207,46 @@
                 case '0':
                 case '^':
                 case '#':
-                    modifier = currentByteAsChar;
+                    modifier = currentChar;
                     continue;
                 case 'e':
-                    outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"),
                             wallTime.getMonthDay());
                     return false;
                 case 'F':
                     formatInternal("%Y-%m-%d", wallTime, zoneInfo);
                     return false;
                 case 'H':
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
                             wallTime.getHour());
                     return false;
                 case 'I':
                     int hour = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12;
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), hour);
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), hour);
                     return false;
                 case 'j':
                     int yearDay = wallTime.getYearDay() + 1;
-                    outputFormatter.format(getFormat(modifier, "%03d", "%3d", "%d", "%03d"),
+                    numberFormatter.format(getFormat(modifier, "%03d", "%3d", "%d", "%03d"),
                             yearDay);
                     return false;
                 case 'k':
-                    outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"),
                             wallTime.getHour());
                     return false;
                 case 'l':
                     int n2 = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12;
-                    outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), n2);
+                    numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), n2);
                     return false;
                 case 'M':
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
                             wallTime.getMinute());
                     return false;
                 case 'm':
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
                             wallTime.getMonth() + 1);
                     return false;
                 case 'n':
-                    modifyAndAppend("\n", modifier);
+                    outputBuilder.append('\n');
                     return false;
                 case 'p':
                     modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1]
@@ -274,27 +263,27 @@
                     formatInternal("%I:%M:%S %p", wallTime, zoneInfo);
                     return false;
                 case 'S':
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
                             wallTime.getSecond());
                     return false;
                 case 's':
                     int timeInSeconds = wallTime.mktime(zoneInfo);
-                    modifyAndAppend(Integer.toString(timeInSeconds), modifier);
+                    outputBuilder.append(Integer.toString(timeInSeconds));
                     return false;
                 case 'T':
                     formatInternal("%H:%M:%S", wallTime, zoneInfo);
                     return false;
                 case 't':
-                    modifyAndAppend("\t", modifier);
+                    outputBuilder.append('\t');
                     return false;
                 case 'U':
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
                             (wallTime.getYearDay() + DAYSPERWEEK - wallTime.getWeekDay())
                                     / DAYSPERWEEK);
                     return false;
                 case 'u':
                     int day = (wallTime.getWeekDay() == 0) ? DAYSPERWEEK : wallTime.getWeekDay();
-                    outputFormatter.format("%d", day);
+                    numberFormatter.format("%d", day);
                     return false;
                 case 'V':   /* ISO 8601 week number */
                 case 'G':   /* ISO 8601 year (four digits) */
@@ -326,9 +315,9 @@
                         --year;
                         yday += isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR;
                     }
-                    if (currentByteAsChar == 'V') {
-                        outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), w);
-                    } else if (currentByteAsChar == 'g') {
+                    if (currentChar == 'V') {
+                        numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), w);
+                    } else if (currentChar == 'g') {
                         outputYear(year, false, true, modifier);
                     } else {
                         outputYear(year, true, true, modifier);
@@ -342,10 +331,10 @@
                     int n = (wallTime.getYearDay() + DAYSPERWEEK - (
                                     wallTime.getWeekDay() != 0 ? (wallTime.getWeekDay() - 1)
                                             : (DAYSPERWEEK - 1))) / DAYSPERWEEK;
-                    outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n);
+                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n);
                     return false;
                 case 'w':
-                    outputFormatter.format("%d", wallTime.getWeekDay());
+                    numberFormatter.format("%d", wallTime.getWeekDay());
                     return false;
                 case 'X':
                     formatInternal(timeOnlyFormat, wallTime, zoneInfo);
@@ -371,17 +360,17 @@
                         return false;
                     }
                     int diff = wallTime.getGmtOffset();
-                    String sign;
+                    char sign;
                     if (diff < 0) {
-                        sign = "-";
+                        sign = '-';
                         diff = -diff;
                     } else {
-                        sign = "+";
+                        sign = '+';
                     }
-                    modifyAndAppend(sign, modifier);
+                    outputBuilder.append(sign);
                     diff /= SECSPERMIN;
                     diff = (diff / MINSPERHOUR) * 100 + (diff % MINSPERHOUR);
-                    outputFormatter.format(getFormat(modifier, "%04d", "%4d", "%d", "%04d"), diff);
+                    numberFormatter.format(getFormat(modifier, "%04d", "%4d", "%d", "%04d"), diff);
                     return false;
                 }
                 case '+':
@@ -422,7 +411,6 @@
                 break;
             default:
                 outputBuilder.append(str);
-
         }
     }
 
@@ -443,14 +431,14 @@
         }
         if (outputTop) {
             if (lead == 0 && trail < 0) {
-                modifyAndAppend("-0", modifier);
+                outputBuilder.append("-0");
             } else {
-                outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), lead);
+                numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), lead);
             }
         }
         if (outputBottom) {
             int n = ((trail < 0) ? -trail : trail);
-            outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n);
+            numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n);
         }
     }
 
@@ -472,24 +460,24 @@
     }
 
     /**
-     * A broken implementation of {@link Character#isUpperCase(char)} that assumes ASCII in order to
-     * be compatible with the old native implementation.
+     * A broken implementation of {@link Character#isUpperCase(char)} that assumes ASCII codes in
+     * order to be compatible with the old native implementation.
      */
     private static boolean brokenIsUpper(char toCheck) {
         return toCheck >= 'A' && toCheck <= 'Z';
     }
 
     /**
-     * A broken implementation of {@link Character#isLowerCase(char)} that assumes ASCII in order to
-     * be compatible with the old native implementation.
+     * A broken implementation of {@link Character#isLowerCase(char)} that assumes ASCII codes in
+     * order to be compatible with the old native implementation.
      */
     private static boolean brokenIsLower(char toCheck) {
         return toCheck >= 'a' && toCheck <= 'z';
     }
 
     /**
-     * A broken implementation of {@link Character#toLowerCase(char)} that assumes ASCII in order to
-     * be compatible with the old native implementation.
+     * A broken implementation of {@link Character#toLowerCase(char)} that assumes ASCII codes in
+     * order to be compatible with the old native implementation.
      */
     private static char brokenToLower(char input) {
         if (input >= 'A' && input <= 'Z') {
@@ -499,8 +487,8 @@
     }
 
     /**
-     * A broken implementation of {@link Character#toUpperCase(char)} that assumes ASCII in order to
-     * be compatible with the old native implementation.
+     * A broken implementation of {@link Character#toUpperCase(char)} that assumes ASCII codes in
+     * order to be compatible with the old native implementation.
      */
     private static char brokenToUpper(char input) {
         if (input >= 'a' && input <= 'z') {
@@ -509,11 +497,4 @@
         return input;
     }
 
-    /**
-     * Safely convert a byte containing an ASCII character to a char, even for character codes
-     * > 127.
-     */
-    private static char convertToChar(byte b) {
-        return (char) (b & 0xFF);
-    }
 }
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index ebb1a5c..eb17429 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -31,6 +31,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.IntProperty;
 import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup;
@@ -185,25 +186,36 @@
             }
             if (numChanges > 0) {
                 if (!mResizeClip) {
-                    if (startLeft != endLeft) view.setLeft(startLeft);
-                    if (startTop != endTop) view.setTop(startTop);
-                    if (startRight != endRight) view.setRight(startRight);
-                    if (startBottom != endBottom) view.setBottom(startBottom);
-                    ObjectAnimator topLeftAnimator = null;
-                    if (startLeft != endLeft || startTop != endTop) {
-                        Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
-                                endLeft, endTop);
-                        topLeftAnimator = ObjectAnimator.ofInt(view, "left", "top", topLeftPath);
+                    Animator anim;
+                    if (startWidth == endWidth && startHeight == endHeight) {
+                        view.offsetLeftAndRight(startLeft - view.getLeft());
+                        view.offsetTopAndBottom(startTop - view.getTop());
+                        Path positionPath = getPathMotion().getPath(0, 0, endLeft - startLeft,
+                                endTop - startTop);
+                        anim = ObjectAnimator.ofInt(view, new HorizontalOffsetProperty(),
+                                new VerticalOffsetProperty(), positionPath);
+                    } else {
+                        if (startLeft != endLeft) view.setLeft(startLeft);
+                        if (startTop != endTop) view.setTop(startTop);
+                        if (startRight != endRight) view.setRight(startRight);
+                        if (startBottom != endBottom) view.setBottom(startBottom);
+                        ObjectAnimator topLeftAnimator = null;
+                        if (startLeft != endLeft || startTop != endTop) {
+                            Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
+                                    endLeft, endTop);
+                            topLeftAnimator = ObjectAnimator
+                                    .ofInt(view, "left", "top", topLeftPath);
+                        }
+                        ObjectAnimator bottomRightAnimator = null;
+                        if (startRight != endRight || startBottom != endBottom) {
+                            Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
+                                    endRight, endBottom);
+                            bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
+                                    bottomRightPath);
+                        }
+                        anim = TransitionUtils.mergeAnimators(topLeftAnimator,
+                                bottomRightAnimator);
                     }
-                    ObjectAnimator bottomRightAnimator = null;
-                    if (startRight != endRight || startBottom != endBottom) {
-                        Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
-                                endRight, endBottom);
-                        bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
-                                bottomRightPath);
-                    }
-                    Animator anim = TransitionUtils.mergeAnimators(topLeftAnimator,
-                            bottomRightAnimator);
                     if (view.getParent() instanceof ViewGroup) {
                         final ViewGroup parent = (ViewGroup) view.getParent();
                         parent.suppressLayout(true);
@@ -341,4 +353,48 @@
         }
         return null;
     }
+
+    private abstract static class OffsetProperty extends IntProperty<View> {
+        int mPreviousValue;
+
+        public OffsetProperty(String name) {
+            super(name);
+        }
+
+        @Override
+        public void setValue(View view, int value) {
+            int offset = value - mPreviousValue;
+            offsetBy(view, offset);
+            mPreviousValue = value;
+        }
+
+        @Override
+        public Integer get(View object) {
+            return null;
+        }
+
+        protected abstract void offsetBy(View view, int by);
+    }
+
+    private static class HorizontalOffsetProperty extends OffsetProperty {
+        public HorizontalOffsetProperty() {
+            super("offsetLeftAndRight");
+        }
+
+        @Override
+        protected void offsetBy(View view, int by) {
+            view.offsetLeftAndRight(by);
+        }
+    }
+
+    private static class VerticalOffsetProperty extends OffsetProperty {
+        public VerticalOffsetProperty() {
+            super("offsetTopAndBottom");
+        }
+
+        @Override
+        protected void offsetBy(View view, int by) {
+            view.offsetTopAndBottom(by);
+        }
+    }
 }
diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java
index ed027eb..41a2e5d 100644
--- a/core/java/android/util/Spline.java
+++ b/core/java/android/util/Spline.java
@@ -20,15 +20,34 @@
  * Performs spline interpolation given a set of control points.
  * @hide
  */
-public final class Spline {
-    private final float[] mX;
-    private final float[] mY;
-    private final float[] mM;
+public abstract class Spline {
 
-    private Spline(float[] x, float[] y, float[] m) {
-        mX = x;
-        mY = y;
-        mM = m;
+    /**
+     * Interpolates the value of Y = f(X) for given X.
+     * Clamps X to the domain of the spline.
+     *
+     * @param x The X value.
+     * @return The interpolated Y = f(X) value.
+     */
+    public abstract float interpolate(float x);
+
+    /**
+     * Creates an appropriate spline based on the properties of the control points.
+     *
+     * If the control points are monotonic then the resulting spline will preserve that and
+     * otherwise optimize for error bounds.
+     */
+    public static Spline createSpline(float[] x, float[] y) {
+        if (!isStrictlyIncreasing(x)) {
+            throw new IllegalArgumentException("The control points must all have strictly "
+                    + "increasing X values.");
+        }
+
+        if (isMonotonic(y)) {
+            return createMonotoneCubicSpline(x, y);
+        } else {
+            return createLinearSpline(x, y);
+        }
     }
 
     /**
@@ -50,107 +69,229 @@
      * @throws IllegalArgumentException if the control points are not monotonic.
      */
     public static Spline createMonotoneCubicSpline(float[] x, float[] y) {
-        if (x == null || y == null || x.length != y.length || x.length < 2) {
-            throw new IllegalArgumentException("There must be at least two control "
-                    + "points and the arrays must be of equal length.");
-        }
-
-        final int n = x.length;
-        float[] d = new float[n - 1]; // could optimize this out
-        float[] m = new float[n];
-
-        // Compute slopes of secant lines between successive points.
-        for (int i = 0; i < n - 1; i++) {
-            float h = x[i + 1] - x[i];
-            if (h <= 0f) {
-                throw new IllegalArgumentException("The control points must all "
-                        + "have strictly increasing X values.");
-            }
-            d[i] = (y[i + 1] - y[i]) / h;
-        }
-
-        // Initialize the tangents as the average of the secants.
-        m[0] = d[0];
-        for (int i = 1; i < n - 1; i++) {
-            m[i] = (d[i - 1] + d[i]) * 0.5f;
-        }
-        m[n - 1] = d[n - 2];
-
-        // Update the tangents to preserve monotonicity.
-        for (int i = 0; i < n - 1; i++) {
-            if (d[i] == 0f) { // successive Y values are equal
-                m[i] = 0f;
-                m[i + 1] = 0f;
-            } else {
-                float a = m[i] / d[i];
-                float b = m[i + 1] / d[i];
-                if (a < 0f || b < 0f) {
-                    throw new IllegalArgumentException("The control points must have "
-                            + "monotonic Y values.");
-                }
-                float h = FloatMath.hypot(a, b);
-                if (h > 9f) {
-                    float t = 3f / h;
-                    m[i] = t * a * d[i];
-                    m[i + 1] = t * b * d[i];
-                }
-            }
-        }
-        return new Spline(x, y, m);
+        return new MonotoneCubicSpline(x, y);
     }
 
     /**
-     * Interpolates the value of Y = f(X) for given X.
-     * Clamps X to the domain of the spline.
+     * Creates a linear spline from a given set of control points.
      *
-     * @param x The X value.
-     * @return The interpolated Y = f(X) value.
+     * Like a monotone cubic spline, the interpolated curve will be monotonic if the control points
+     * are monotonic.
+     *
+     * @param x The X component of the control points, strictly increasing.
+     * @param y The Y component of the control points.
+     * @return
+     *
+     * @throws IllegalArgumentException if the X or Y arrays are null, have
+     * different lengths or have fewer than 2 values.
+     * @throws IllegalArgumentException if the X components of the control points are not strictly
+     * increasing.
      */
-    public float interpolate(float x) {
-        // Handle the boundary cases.
-        final int n = mX.length;
-        if (Float.isNaN(x)) {
-            return x;
-        }
-        if (x <= mX[0]) {
-            return mY[0];
-        }
-        if (x >= mX[n - 1]) {
-            return mY[n - 1];
-        }
-
-        // Find the index 'i' of the last point with smaller X.
-        // We know this will be within the spline due to the boundary tests.
-        int i = 0;
-        while (x >= mX[i + 1]) {
-            i += 1;
-            if (x == mX[i]) {
-                return mY[i];
-            }
-        }
-
-        // Perform cubic Hermite spline interpolation.
-        float h = mX[i + 1] - mX[i];
-        float t = (x - mX[i]) / h;
-        return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
-                + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
+    public static Spline createLinearSpline(float[] x, float[] y) {
+        return new LinearSpline(x, y);
     }
 
-    // For debugging.
-    @Override
-    public String toString() {
-        StringBuilder str = new StringBuilder();
-        final int n = mX.length;
-        str.append("[");
-        for (int i = 0; i < n; i++) {
-            if (i != 0) {
-                str.append(", ");
-            }
-            str.append("(").append(mX[i]);
-            str.append(", ").append(mY[i]);
-            str.append(": ").append(mM[i]).append(")");
+    private static boolean isStrictlyIncreasing(float[] x) {
+        if (x == null || x.length < 2) {
+            throw new IllegalArgumentException("There must be at least two control points.");
         }
-        str.append("]");
-        return str.toString();
+        float prev = x[0];
+        for (int i = 1; i < x.length; i++) {
+            float curr = x[i];
+            if (curr <= prev) {
+                return false;
+            }
+            prev = curr;
+        }
+        return true;
+    }
+
+    private static boolean isMonotonic(float[] x) {
+        if (x == null || x.length < 2) {
+            throw new IllegalArgumentException("There must be at least two control points.");
+        }
+        float prev = x[0];
+        for (int i = 1; i < x.length; i++) {
+            float curr = x[i];
+            if (curr < prev) {
+                return false;
+            }
+            prev = curr;
+        }
+        return true;
+    }
+
+    public static class MonotoneCubicSpline extends Spline {
+        private float[] mX;
+        private float[] mY;
+        private float[] mM;
+
+        public MonotoneCubicSpline(float[] x, float[] y) {
+            if (x == null || y == null || x.length != y.length || x.length < 2) {
+                throw new IllegalArgumentException("There must be at least two control "
+                        + "points and the arrays must be of equal length.");
+            }
+
+            final int n = x.length;
+            float[] d = new float[n - 1]; // could optimize this out
+            float[] m = new float[n];
+
+            // Compute slopes of secant lines between successive points.
+            for (int i = 0; i < n - 1; i++) {
+                float h = x[i + 1] - x[i];
+                if (h <= 0f) {
+                    throw new IllegalArgumentException("The control points must all "
+                            + "have strictly increasing X values.");
+                }
+                d[i] = (y[i + 1] - y[i]) / h;
+            }
+
+            // Initialize the tangents as the average of the secants.
+            m[0] = d[0];
+            for (int i = 1; i < n - 1; i++) {
+                m[i] = (d[i - 1] + d[i]) * 0.5f;
+            }
+            m[n - 1] = d[n - 2];
+
+            // Update the tangents to preserve monotonicity.
+            for (int i = 0; i < n - 1; i++) {
+                if (d[i] == 0f) { // successive Y values are equal
+                    m[i] = 0f;
+                    m[i + 1] = 0f;
+                } else {
+                    float a = m[i] / d[i];
+                    float b = m[i + 1] / d[i];
+                    if (a < 0f || b < 0f) {
+                        throw new IllegalArgumentException("The control points must have "
+                                + "monotonic Y values.");
+                    }
+                    float h = FloatMath.hypot(a, b);
+                    if (h > 9f) {
+                        float t = 3f / h;
+                        m[i] = t * a * d[i];
+                        m[i + 1] = t * b * d[i];
+                    }
+                }
+            }
+
+            mX = x;
+            mY = y;
+            mM = m;
+        }
+
+        @Override
+        public float interpolate(float x) {
+            // Handle the boundary cases.
+            final int n = mX.length;
+            if (Float.isNaN(x)) {
+                return x;
+            }
+            if (x <= mX[0]) {
+                return mY[0];
+            }
+            if (x >= mX[n - 1]) {
+                return mY[n - 1];
+            }
+
+            // Find the index 'i' of the last point with smaller X.
+            // We know this will be within the spline due to the boundary tests.
+            int i = 0;
+            while (x >= mX[i + 1]) {
+                i += 1;
+                if (x == mX[i]) {
+                    return mY[i];
+                }
+            }
+
+            // Perform cubic Hermite spline interpolation.
+            float h = mX[i + 1] - mX[i];
+            float t = (x - mX[i]) / h;
+            return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
+                    + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
+        }
+
+        // For debugging.
+        @Override
+        public String toString() {
+            StringBuilder str = new StringBuilder();
+            final int n = mX.length;
+            str.append("MonotoneCubicSpline{[");
+            for (int i = 0; i < n; i++) {
+                if (i != 0) {
+                    str.append(", ");
+                }
+                str.append("(").append(mX[i]);
+                str.append(", ").append(mY[i]);
+                str.append(": ").append(mM[i]).append(")");
+            }
+            str.append("]}");
+            return str.toString();
+        }
+    }
+
+    public static class LinearSpline extends Spline {
+        private final float[] mX;
+        private final float[] mY;
+        private final float[] mM;
+
+        public LinearSpline(float[] x, float[] y) {
+            if (x == null || y == null || x.length != y.length || x.length < 2) {
+                throw new IllegalArgumentException("There must be at least two control "
+                        + "points and the arrays must be of equal length.");
+            }
+            final int N = x.length;
+            mM = new float[N-1];
+            for (int i = 0; i < N-1; i++) {
+                mM[i] = (y[i+1] - y[i]) / (x[i+1] - x[i]);
+            }
+            mX = x;
+            mY = y;
+        }
+
+        @Override
+        public float interpolate(float x) {
+            // Handle the boundary cases.
+            final int n = mX.length;
+            if (Float.isNaN(x)) {
+                return x;
+            }
+            if (x <= mX[0]) {
+                return mY[0];
+            }
+            if (x >= mX[n - 1]) {
+                return mY[n - 1];
+            }
+
+            // Find the index 'i' of the last point with smaller X.
+            // We know this will be within the spline due to the boundary tests.
+            int i = 0;
+            while (x >= mX[i + 1]) {
+                i += 1;
+                if (x == mX[i]) {
+                    return mY[i];
+                }
+            }
+            return mY[i] + mM[i] * (x - mX[i]);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder str = new StringBuilder();
+            final int n = mX.length;
+            str.append("LinearSpline{[");
+            for (int i = 0; i < n; i++) {
+                if (i != 0) {
+                    str.append(", ");
+                }
+                str.append("(").append(mX[i]);
+                str.append(", ").append(mY[i]);
+                if (i < n-1) {
+                    str.append(": ").append(mM[i]);
+                }
+                str.append(")");
+            }
+            str.append("]}");
+            return str.toString();
+        }
     }
 }
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 413ea04..fa4a13a 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -87,8 +87,11 @@
     private float mFinalValue;
     private TimeInterpolator mInterpolator;
 
-    private boolean mStarted = false;
-    private boolean mFinished = false;
+    private static final int STATE_PREPARE = 0;
+    private static final int STATE_DELAYED = 1;
+    private static final int STATE_RUNNING = 2;
+    private static final int STATE_FINISHED = 3;
+    private int mState = STATE_PREPARE;
 
     private long mUnscaledDuration = 300;
     private long mUnscaledStartDelay = 0;
@@ -142,7 +145,7 @@
     }
 
     private void checkMutable() {
-        if (mStarted) {
+        if (mState != STATE_PREPARE) {
             throw new IllegalStateException("Animator has already started, cannot change it now!");
         }
     }
@@ -170,11 +173,11 @@
             throw new IllegalStateException("Missing target!");
         }
 
-        if (mStarted) {
+        if (mState != STATE_PREPARE) {
             throw new IllegalStateException("Already started!");
         }
 
-        mStarted = true;
+        mState = STATE_DELAYED;
         applyInterpolator();
 
         if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
@@ -186,6 +189,7 @@
     }
 
     private void doStart() {
+        mState = STATE_RUNNING;
         nStart(mNativePtr.get(), this);
 
         // Alpha is a special snowflake that has the canonical value stored
@@ -197,11 +201,7 @@
             mViewTarget.mTransformationInfo.mAlpha = mFinalValue;
         }
 
-        final ArrayList<AnimatorListener> listeners = cloneListeners();
-        final int numListeners = listeners == null ? 0 : listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            listeners.get(i).onAnimationStart(this);
-        }
+        notifyStartListeners();
 
         if (mViewTarget != null) {
             // Kick off a frame to start the process
@@ -209,10 +209,21 @@
         }
     }
 
+    private void notifyStartListeners() {
+        final ArrayList<AnimatorListener> listeners = cloneListeners();
+        final int numListeners = listeners == null ? 0 : listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            listeners.get(i).onAnimationStart(this);
+        }
+    }
+
     @Override
     public void cancel() {
-        if (!mFinished) {
-            getHelper().removeDelayedAnimation(this);
+        if (mState != STATE_FINISHED) {
+            if (mState == STATE_DELAYED) {
+                getHelper().removeDelayedAnimation(this);
+                notifyStartListeners();
+            }
             nEnd(mNativePtr.get());
 
             final ArrayList<AnimatorListener> listeners = cloneListeners();
@@ -220,12 +231,17 @@
             for (int i = 0; i < numListeners; i++) {
                 listeners.get(i).onAnimationCancel(this);
             }
+
+            if (mViewTarget != null) {
+                // Kick off a frame to flush the state change
+                mViewTarget.invalidateViewProperty(true, false);
+            }
         }
     }
 
     @Override
     public void end() {
-        if (!mFinished) {
+        if (mState != STATE_FINISHED) {
             nEnd(mNativePtr.get());
         }
     }
@@ -299,12 +315,12 @@
 
     @Override
     public boolean isRunning() {
-        return mStarted && !mFinished;
+        return mState == STATE_DELAYED || mState == STATE_RUNNING;
     }
 
     @Override
     public boolean isStarted() {
-        return mStarted;
+        return mState != STATE_PREPARE;
     }
 
     @Override
@@ -319,7 +335,11 @@
     }
 
     protected void onFinished() {
-        mFinished = true;
+        if (mState == STATE_DELAYED) {
+            getHelper().removeDelayedAnimation(this);
+            notifyStartListeners();
+        }
+        mState = STATE_FINISHED;
 
         final ArrayList<AnimatorListener> listeners = cloneListeners();
         final int numListeners = listeners == null ? 0 : listeners.size();
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 602f955..56bdb9b 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -176,7 +176,7 @@
                 RemoteViewsAdapter adapter;
                 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
                 if ((adapter = mAdapter.get()) != null) {
-                    mgr.unbindRemoteViewsService(context.getPackageName(), appWidgetId, intent);
+                    mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent);
                 } else {
                     Slog.w(TAG, "unbind: adapter was null");
                 }
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 818efaa..ece8aa4 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -244,6 +244,16 @@
         // Set the default context, since setPopupTheme() may be a no-op.
         mPopupContext = mContext;
         setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0));
+
+        final Drawable navIcon = a.getDrawable(R.styleable.Toolbar_navigationIcon);
+        if (navIcon != null) {
+            setNavigationIcon(navIcon);
+            final CharSequence navDesc = a.getText(
+                    R.styleable.Toolbar_navigationContentDescription);
+            if (!TextUtils.isEmpty(navDesc)) {
+                setNavigationContentDescription(navDesc);
+            }
+        }
         a.recycle();
     }
 
@@ -669,6 +679,8 @@
      * as screen readers or tooltips.
      *
      * @return The navigation button's content description
+     *
+     * @attr ref android.R.styleable#Toolbar_navigationContentDescription
      */
     @Nullable
     public CharSequence getNavigationContentDescription() {
@@ -682,6 +694,8 @@
      *
      * @param resId Resource ID of a content description string to set, or 0 to
      *              clear the description
+     *
+     * @attr ref android.R.styleable#Toolbar_navigationContentDescription
      */
     public void setNavigationContentDescription(int resId) {
         setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
@@ -694,6 +708,8 @@
      *
      * @param description Content description to set, or <code>null</code> to
      *                    clear the content description
+     *
+     * @attr ref android.R.styleable#Toolbar_navigationContentDescription
      */
     public void setNavigationContentDescription(@Nullable CharSequence description) {
         if (!TextUtils.isEmpty(description)) {
@@ -715,6 +731,8 @@
      * tooltips.</p>
      *
      * @param resId Resource ID of a drawable to set
+     *
+     * @attr ref android.R.styleable#Toolbar_navigationIcon
      */
     public void setNavigationIcon(int resId) {
         setNavigationIcon(getContext().getDrawable(resId));
@@ -731,6 +749,8 @@
      * tooltips.</p>
      *
      * @param icon Drawable to set, may be null to clear the icon
+     *
+     * @attr ref android.R.styleable#Toolbar_navigationIcon
      */
     public void setNavigationIcon(@Nullable Drawable icon) {
         if (icon != null) {
@@ -751,6 +771,8 @@
      * Return the current drawable used as the navigation icon.
      *
      * @return The navigation icon drawable
+     *
+     * @attr ref android.R.styleable#Toolbar_navigationIcon
      */
     @Nullable
     public Drawable getNavigationIcon() {
@@ -1316,6 +1338,8 @@
             final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView;
             final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams();
             final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams();
+            final boolean titleHasWidth = layoutTitle && mTitleTextView.getMeasuredWidth() > 0
+                    || layoutSubtitle && mSubtitleTextView.getMeasuredWidth() > 0;
 
             switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
                 case Gravity.TOP:
@@ -1343,7 +1367,7 @@
                     break;
             }
             if (isRtl) {
-                final int rd = mTitleMarginStart - collapsingMargins[1];
+                final int rd = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[1];
                 right -= Math.max(0, rd);
                 collapsingMargins[1] = Math.max(0, -rd);
                 int titleRight = right;
@@ -1366,9 +1390,11 @@
                     subtitleRight = subtitleRight - mTitleMarginEnd;
                     titleTop = subtitleBottom + lp.bottomMargin;
                 }
-                right = Math.min(titleRight, subtitleRight);
+                if (titleHasWidth) {
+                    right = Math.min(titleRight, subtitleRight);
+                }
             } else {
-                final int ld = mTitleMarginStart - collapsingMargins[0];
+                final int ld = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[0];
                 left += Math.max(0, ld);
                 collapsingMargins[0] = Math.max(0, -ld);
                 int titleLeft = left;
@@ -1391,7 +1417,9 @@
                     subtitleLeft = subtitleRight + mTitleMarginEnd;
                     titleTop = subtitleBottom + lp.bottomMargin;
                 }
-                left = Math.max(titleLeft, subtitleLeft);
+                if (titleHasWidth) {
+                    left = Math.max(titleLeft, subtitleLeft);
+                }
             }
         }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 61b4567..b6e7353 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -23,7 +23,6 @@
 import android.os.AsyncTask;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Slog;
 import android.widget.AbsListView;
 import android.widget.GridView;
@@ -73,6 +72,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -100,7 +100,7 @@
     private boolean mResolvingHome = false;
 
     private UsageStatsManager mUsm;
-    private ArrayMap<String, UsageStats> mStats;
+    private Map<String, UsageStats> mStats;
     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
 
     private boolean mRegistered;
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 2967938..ba236f3 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -191,6 +191,20 @@
     public boolean requestUpdateCursorAnchorInfo(int cursorUpdateMode) {
         if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
 
+        // It is possible that any other bit is used as a valid flag in a future release.
+        // We should reject the entire request in such a case.
+        final int KNOWN_FLAGS_MASK = InputConnection.REQUEST_UPDATE_CURSOR_ANCHOR_INFO_IMMEDIATE |
+                InputConnection.REQUEST_UPDATE_CURSOR_ANCHOR_INFO_MONITOR;
+        final int unknownFlags = cursorUpdateMode & ~KNOWN_FLAGS_MASK;
+        if (unknownFlags != 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags." +
+                        " cursorUpdateMode=" + cursorUpdateMode +
+                        " unknownFlags=" + unknownFlags);
+            }
+            return false;
+        }
+
         if (mIMM == null) {
             // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled.
             // TODO: Return some notification code rather than false to indicate method that
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 3595033..ef916ed1 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -53,8 +53,8 @@
                 android:visibility="gone"
                 />
             <ImageView android:id="@+id/profile_badge_large_template"
-                android:layout_width="20dp"
-                android:layout_height="20dp"
+                android:layout_width="@dimen/notification_badge_size"
+                android:layout_height="@dimen/notification_badge_size"
                 android:layout_weight="0"
                 android:layout_marginStart="4dp"
                 android:scaleType="fitCenter"
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index d601d4a3..3415814 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -39,11 +39,12 @@
         <include layout="@layout/notification_template_part_line2" />
         <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="0dp"
             android:layout_marginEnd="8dp"
             android:layout_marginBottom="10dp"
             android:orientation="horizontal"
             android:gravity="top"
+            android:layout_weight="1"
             >
             <TextView android:id="@+id/big_text"
                 android:textAppearance="@style/TextAppearance.Material.Notification"
@@ -54,8 +55,8 @@
                 android:visibility="gone"
                 />
             <ImageView android:id="@+id/profile_badge_large_template"
-                android:layout_width="20dp"
-                android:layout_height="20dp"
+                android:layout_width="@dimen/notification_badge_size"
+                android:layout_height="@dimen/notification_badge_size"
                 android:layout_weight="0"
                 android:layout_marginStart="4dp"
                 android:scaleType="fitCenter"
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index 2bd9f81..2382d18 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -37,95 +37,27 @@
         >
         <include layout="@layout/notification_template_part_line1" />
         <include layout="@layout/notification_template_part_line2" />
+
+        <!-- We can't have another vertical linear layout here with weight != 0 so this forces us to
+             put the badge on the first line. -->
         <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_height="0dp"
             android:orientation="horizontal"
-            android:gravity="top"
             >
-            <LinearLayout
+            <TextView android:id="@+id/inbox_text0"
+                android:textAppearance="@style/TextAppearance.Material.Notification"
                 android:layout_width="0dp"
-                android:layout_weight="1"
                 android:layout_height="wrap_content"
-                android:orientation="vertical"
-                >
-                <TextView android:id="@+id/inbox_text0"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    />
-                <TextView android:id="@+id/inbox_text1"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    />
-                <TextView android:id="@+id/inbox_text2"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    />
-                <TextView android:id="@+id/inbox_text3"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    />
-                <TextView android:id="@+id/inbox_text4"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    />
-                <TextView android:id="@+id/inbox_text5"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    />
-                <TextView android:id="@+id/inbox_text6"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    />
-                <TextView android:id="@+id/inbox_more"
-                    android:textAppearance="@style/TextAppearance.Material.Notification"
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:visibility="gone"
-                    android:layout_weight="1"
-                    android:text="@android:string/ellipsis"
-                    />
-            </LinearLayout>
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
             <ImageView android:id="@+id/profile_badge_large_template"
-                android:layout_width="20dp"
-                android:layout_height="20dp"
+                android:layout_width="@dimen/notification_badge_size"
+                android:layout_height="@dimen/notification_badge_size"
                 android:layout_weight="0"
                 android:layout_marginStart="4dp"
                 android:layout_marginEnd="8dp"
@@ -133,6 +65,77 @@
                 android:visibility="gone"
                 />
         </LinearLayout>
+        <TextView android:id="@+id/inbox_text1"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_text2"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_text3"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_text4"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_text5"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_text6"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_more"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            android:text="@android:string/ellipsis"
+            />
         <FrameLayout
             android:id="@+id/inbox_end_pad"
             android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_part_line2.xml b/core/res/res/layout/notification_template_part_line2.xml
index 28d2bef..7e99c5e 100644
--- a/core/res/res/layout/notification_template_part_line2.xml
+++ b/core/res/res/layout/notification_template_part_line2.xml
@@ -20,8 +20,6 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginEnd="8dp"
-        android:visibility="gone"
-        android:layout_weight="0"
         android:orientation="horizontal"
         android:gravity="center_vertical"
         >
@@ -39,8 +37,8 @@
             android:layout_weight="1"
         />
         <ImageView android:id="@+id/profile_badge_line2"
-            android:layout_width="20dp"
-            android:layout_height="20dp"
+            android:layout_width="@dimen/notification_badge_size"
+            android:layout_height="@dimen/notification_badge_size"
             android:layout_weight="0"
             android:layout_marginStart="4dp"
             android:scaleType="fitCenter"
diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml
index 56de5c9..6c043a0 100644
--- a/core/res/res/layout/notification_template_part_line3.xml
+++ b/core/res/res/layout/notification_template_part_line3.xml
@@ -44,8 +44,8 @@
         android:paddingStart="8dp"
         />
     <ImageView android:id="@+id/profile_badge_line3"
-        android:layout_width="20dp"
-        android:layout_height="20dp"
+        android:layout_width="@dimen/notification_badge_size"
+        android:layout_height="@dimen/notification_badge_size"
         android:layout_gravity="center"
         android:layout_weight="0"
         android:layout_marginStart="4dp"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9da116a..a798d2e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7318,6 +7318,12 @@
         <!-- Reference to a theme that should be used to inflate popups
              shown by widgets in the toolbar. -->
         <attr name="popupTheme" format="reference" />
+        <!-- Icon drawable to use for the navigation button located at
+             the start of the toolbar. -->
+        <attr name="navigationIcon" format="reference" />
+        <!-- Text to set as the content description for the navigation button
+             located at the start of the toolbar. -->
+        <attr name="navigationContentDescription" format="string" />
     </declare-styleable>
 
     <declare-styleable name="Toolbar_LayoutParams">
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 4e3abb9..77b451f 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -236,6 +236,9 @@
     <!-- Padding for notification icon when drawn with circle around it -->
     <dimen name="notification_large_icon_circle_padding">11dp</dimen>
 
+    <!-- Size of the profile badge for notifications -->
+    <dimen name="notification_badge_size">16dp</dimen>
+
     <!-- Keyguard dimensions -->
     <!-- TEMP -->
     <dimen name="kg_security_panel_height">600dp</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 4c59f73..bd24f3e 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -87,4 +87,6 @@
   <item type="id" name="transitionPosition" />
   <item type="id" name="transitionTransform" />
   <item type="id" name="parentMatrix" />
+  <item type="id" name="statusBarBackground" />
+  <item type="id" name="navigationBarBackground" />
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3b49bed..5e76a87 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2280,12 +2280,16 @@
   <public type="attr" name="reparentWithOverlay" />
   <public type="attr" name="ambientShadowAlpha" />
   <public type="attr" name="spotShadowAlpha" />
+  <public type="attr" name="navigationIcon" />
+  <public type="attr" name="navigationContentDescription" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
   <public-padding type="id" name="l_resource_pad" end="0x01020040" />
 
   <public type="id" name="mask" />
+  <public type="id" name="statusBarBackground" />
+  <public type="id" name="navigationBarBackground" />
 
   <public-padding type="style" name="l_resource_pad" end="0x01030200" />
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d8c29b0..622a01a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -357,6 +357,7 @@
   <java-symbol type="dimen" name="notification_top_pad_large_text" />
   <java-symbol type="dimen" name="notification_top_pad_large_text_narrow" />
   <java-symbol type="dimen" name="notification_large_icon_circle_padding" />
+  <java-symbol type="dimen" name="notification_badge_size" />
   <java-symbol type="dimen" name="immersive_mode_cling_width" />
   <java-symbol type="dimen" name="circular_display_mask_offset" />
 
diff --git a/docs/html/guide/topics/admin/device-admin.jd b/docs/html/guide/topics/admin/device-admin.jd
index ee6b814..bed4b4d 100644
--- a/docs/html/guide/topics/admin/device-admin.jd
+++ b/docs/html/guide/topics/admin/device-admin.jd
@@ -28,12 +28,6 @@
       <li>{@link android.app.admin.DevicePolicyManager}</li>
       <li>{@link android.app.admin.DeviceAdminInfo}</li>
     </ol>
-    <h2>Related samples</h2>
-    <ol>
-      <li><a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html">
-DeviceAdminSample</a></li>
-</ol>
 </div>
 </div>
 
@@ -232,18 +226,12 @@
 
 <h2 id="sample">Sample Application</h2>
 
-<p>The examples used in this document are based on the <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html">
-Device Administration API
-sample</a>, which is included in the SDK samples. For information on downloading and
-installing the SDK samples, see <a
-href="{@docRoot}resources/samples/get.html">
-Getting the Samples</a>. Here is the  <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html">
-complete code</a> for
-the sample. </p>
-<p>The
-sample application offers a demo of device admin features. It presents users
+<p>The examples used in this document are based on the Device Administration API
+sample, which is included in the SDK samples (available through the
+Android SDK Manager) and located on your system as 
+<code>&lt;sdk_root&gt;/ApiDemos/app/src/main/java/com/example/android/apis/app/DeviceAdminSample.java</code>.</p>
+
+<p>The sample application offers a demo of device admin features. It presents users
 with a user interface that lets them enable the device admin application. Once
 they've enabled the application, they can use the buttons in the user interface
 to do the following:</p>
@@ -676,7 +664,8 @@
 <p>You can also programmatically tell the device to lock immediately:</p>
 <pre>
 DevicePolicyManager mDPM;
-mDPM.lockNow();</pre>
+mDPM.lockNow();
+</pre>
 
 
 
@@ -692,12 +681,12 @@
 <pre>
 DevicePolicyManager mDPM;
 mDPM.wipeData(0);</pre>
-<p>The {@link android.app.admin.DevicePolicyManager#wipeData wipeData()} method takes as its parameter a bit mask of
-additional options. Currently the value must be 0. </p>
+<p>The {@link android.app.admin.DevicePolicyManager#wipeData wipeData()} method takes as its
+  parameter a bit mask of additional options. Currently the value must be 0. </p>
 
 <h4>Disable camera</h4>
 <p>Beginning with Android 4.0, you can disable the camera. Note that this doesn't have to be a permanent disabling. The camera can be enabled/disabled dynamically based on context, time, and so on. </p>
-<p>You control whether the camera is disabled by using the 
+<p>You control whether the camera is disabled by using the
 {@link android.app.admin.DevicePolicyManager#setCameraDisabled(android.content.ComponentName, boolean) setCameraDisabled()} method. For example, this snippet sets the camera to be enabled or disabled based on a checkbox setting:</p>
 
 <pre>private CheckBoxPreference mDisableCameraCheckbox;
@@ -708,8 +697,8 @@
 </pre>
 
 
-<h4 id=storage">Storage encryption</h4>
-<p>Beginning with Android 3.0, you can use the 
+<h4 id="storage">Storage encryption</h4>
+<p>Beginning with Android 3.0, you can use the
 {@link android.app.admin.DevicePolicyManager#setStorageEncryption(android.content.ComponentName,boolean) setStorageEncryption()} 
 method to set a policy requiring encryption of the storage area, where supported.</p>
 
@@ -722,5 +711,5 @@
 mDPM.setStorageEncryption(mDeviceAdminSample, true);
 </pre>
 <p>
-See the <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html"> Device Administration API sample</a> for a complete
-example of how to enable storage encryption.</p>
+See the Device Administration API sample for a complete example of how to enable storage encryption.
+</p>
\ No newline at end of file
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 11568d2..c65efe4 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1557,7 +1557,7 @@
     };
     const char16_t* valueToString(const Res_value* value, size_t stringBlock,
                                   char16_t tmpBuffer[TMP_BUFFER_SIZE],
-                                  size_t* outLen);
+                                  size_t* outLen) const;
 
     struct bag_entry {
         ssize_t stringBlock;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 3f014ef..690b1d6 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2939,7 +2939,7 @@
             for (size_t i = 0; i < bags->size(); i++) {
                 TABLE_NOISY(printf("type=%d\n", i));
                 const TypeList& typeList = types[i];
-                if (typeList.isEmpty()) {
+                if (!typeList.isEmpty()) {
                     bag_set** typeBags = bags->get(i);
                     TABLE_NOISY(printf("typeBags=%p\n", typeBags));
                     if (typeBags) {
@@ -3728,7 +3728,7 @@
 
 const char16_t* ResTable::valueToString(
     const Res_value* value, size_t stringBlock,
-    char16_t /*tmpBuffer*/ [TMP_BUFFER_SIZE], size_t* outLen)
+    char16_t /*tmpBuffer*/ [TMP_BUFFER_SIZE], size_t* outLen) const
 {
     if (!value) {
         return NULL;
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index a10c387..5808d20 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -26,6 +26,7 @@
     Idmap_test.cpp \
     ResTable_test.cpp \
     Split_test.cpp \
+    Theme_test.cpp \
     TypeWrappers_test.cpp \
     ZipUtils_test.cpp
 
diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp
index 17f91ca..92af7fe 100644
--- a/libs/androidfw/tests/BackupData_test.cpp
+++ b/libs/androidfw/tests/BackupData_test.cpp
@@ -45,7 +45,7 @@
 class BackupDataTest : public testing::Test {
 protected:
     char* m_external_storage;
-    char* m_filename;
+    String8 mFilename;
     String8 mKey1;
     String8 mKey2;
     String8 mKey3;
@@ -53,15 +53,13 @@
 
     virtual void SetUp() {
         m_external_storage = getenv("EXTERNAL_STORAGE");
+        mFilename.append(m_external_storage);
+        mFilename.append(TEST_FILENAME);
 
-        const int totalLen = strlen(m_external_storage) + strlen(TEST_FILENAME) + 1;
-        m_filename = new char[totalLen];
-        snprintf(m_filename, totalLen, "%s%s", m_external_storage, TEST_FILENAME);
-
-        ::unlink(m_filename);
-        int fd = ::open(m_filename, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+        ::unlink(mFilename.string());
+        int fd = ::open(mFilename.string(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
         if (fd < 0) {
-            FAIL() << "Couldn't create " << m_filename << " for writing";
+            FAIL() << "Couldn't create " << mFilename.string() << " for writing";
         }
         mKey1 = String8(KEY1);
         mKey2 = String8(KEY2);
@@ -74,7 +72,7 @@
 };
 
 TEST_F(BackupDataTest, WriteAndReadSingle) {
-  int fd = ::open(m_filename, O_WRONLY);
+  int fd = ::open(mFilename.string(), O_WRONLY);
   BackupDataWriter* writer = new BackupDataWriter(fd);
 
   EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1)))
@@ -83,7 +81,7 @@
           << "WriteEntityData returned an error";
 
   ::close(fd);
-  fd = ::open(m_filename, O_RDONLY);
+  fd = ::open(mFilename.string(), O_RDONLY);
   BackupDataReader* reader = new BackupDataReader(fd);
   EXPECT_EQ(NO_ERROR, reader->Status())
           << "Reader ctor failed";
@@ -116,7 +114,7 @@
 }
 
 TEST_F(BackupDataTest, WriteAndReadMultiple) {
-  int fd = ::open(m_filename, O_WRONLY);
+  int fd = ::open(mFilename.string(), O_WRONLY);
   BackupDataWriter* writer = new BackupDataWriter(fd);
   writer->WriteEntityHeader(mKey1, sizeof(DATA1));
   writer->WriteEntityData(DATA1, sizeof(DATA1));
@@ -124,7 +122,7 @@
   writer->WriteEntityData(DATA2, sizeof(DATA2));
 
   ::close(fd);
-  fd = ::open(m_filename, O_RDONLY);
+  fd = ::open(mFilename.string(), O_RDONLY);
   BackupDataReader* reader = new BackupDataReader(fd);
 
   bool done;
@@ -164,7 +162,7 @@
 }
 
 TEST_F(BackupDataTest, SkipEntity) {
-  int fd = ::open(m_filename, O_WRONLY);
+  int fd = ::open(mFilename.string(), O_WRONLY);
   BackupDataWriter* writer = new BackupDataWriter(fd);
   writer->WriteEntityHeader(mKey1, sizeof(DATA1));
   writer->WriteEntityData(DATA1, sizeof(DATA1));
@@ -174,7 +172,7 @@
   writer->WriteEntityData(DATA3, sizeof(DATA3));
 
   ::close(fd);
-  fd = ::open(m_filename, O_RDONLY);
+  fd = ::open(mFilename.string(), O_RDONLY);
   BackupDataReader* reader = new BackupDataReader(fd);
 
   bool done;
@@ -219,14 +217,14 @@
 }
 
 TEST_F(BackupDataTest, DeleteEntity) {
-  int fd = ::open(m_filename, O_WRONLY);
+  int fd = ::open(mFilename.string(), O_WRONLY);
   BackupDataWriter* writer = new BackupDataWriter(fd);
   writer->WriteEntityHeader(mKey1, sizeof(DATA1));
   writer->WriteEntityData(DATA1, sizeof(DATA1));
   writer->WriteEntityHeader(mKey2, -1);
 
   ::close(fd);
-  fd = ::open(m_filename, O_RDONLY);
+  fd = ::open(mFilename.string(), O_RDONLY);
   BackupDataReader* reader = new BackupDataReader(fd);
 
   bool done;
@@ -258,7 +256,7 @@
 }
 
 TEST_F(BackupDataTest, EneityAfterDelete) {
-  int fd = ::open(m_filename, O_WRONLY);
+  int fd = ::open(mFilename.string(), O_WRONLY);
   BackupDataWriter* writer = new BackupDataWriter(fd);
   writer->WriteEntityHeader(mKey1, sizeof(DATA1));
   writer->WriteEntityData(DATA1, sizeof(DATA1));
@@ -267,7 +265,7 @@
   writer->WriteEntityData(DATA3, sizeof(DATA3));
 
   ::close(fd);
-  fd = ::open(m_filename, O_RDONLY);
+  fd = ::open(mFilename.string(), O_RDONLY);
   BackupDataReader* reader = new BackupDataReader(fd);
 
   bool done;
@@ -319,7 +317,7 @@
 }
 
 TEST_F(BackupDataTest, OnlyDeleteEntities) {
-  int fd = ::open(m_filename, O_WRONLY);
+  int fd = ::open(mFilename.string(), O_WRONLY);
   BackupDataWriter* writer = new BackupDataWriter(fd);
   writer->WriteEntityHeader(mKey1, -1);
   writer->WriteEntityHeader(mKey2, -1);
@@ -327,7 +325,7 @@
   writer->WriteEntityHeader(mKey4, -1);
 
   ::close(fd);
-  fd = ::open(m_filename, O_RDONLY);
+  fd = ::open(mFilename.string(), O_RDONLY);
   BackupDataReader* reader = new BackupDataReader(fd);
 
   bool done;
@@ -387,13 +385,13 @@
 }
 
 TEST_F(BackupDataTest, ReadDeletedEntityData) {
-  int fd = ::open(m_filename, O_WRONLY);
+  int fd = ::open(mFilename.string(), O_WRONLY);
   BackupDataWriter* writer = new BackupDataWriter(fd);
   writer->WriteEntityHeader(mKey1, -1);
   writer->WriteEntityHeader(mKey2, -1);
 
   ::close(fd);
-  fd = ::open(m_filename, O_RDONLY);
+  fd = ::open(mFilename.string(), O_RDONLY);
   BackupDataReader* reader = new BackupDataReader(fd);
 
   bool done;
@@ -431,6 +429,7 @@
   EXPECT_EQ(-1, (int) dataSize)
           << "not recognizing deletion on second entity";
 
+  delete[] dataBytes;
   delete writer;
   delete reader;
 }
diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp
index 7a4dd13..1151121 100644
--- a/libs/androidfw/tests/ObbFile_test.cpp
+++ b/libs/androidfw/tests/ObbFile_test.cpp
@@ -34,20 +34,18 @@
 class ObbFileTest : public testing::Test {
 protected:
     sp<ObbFile> mObbFile;
-    char* mExternalStorage;
-    char* mFileName;
+    String8 mFileName;
 
     virtual void SetUp() {
         mObbFile = new ObbFile();
-        mExternalStorage = getenv("EXTERNAL_STORAGE");
+        char* externalStorage = getenv("EXTERNAL_STORAGE");
 
-        const int totalLen = strlen(mExternalStorage) + strlen(TEST_FILENAME) + 1;
-        mFileName = new char[totalLen];
-        snprintf(mFileName, totalLen, "%s%s", mExternalStorage, TEST_FILENAME);
+        mFileName.append(externalStorage);
+        mFileName.append(TEST_FILENAME);
 
-        int fd = ::open(mFileName, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+        int fd = ::open(mFileName.string(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
         if (fd < 0) {
-            FAIL() << "Couldn't create " << mFileName << " for tests";
+            FAIL() << "Couldn't create " << mFileName.string() << " for tests";
         }
     }
 
@@ -71,12 +69,12 @@
     EXPECT_TRUE(mObbFile->setSalt(salt, SALT_SIZE))
             << "Salt should be successfully set";
 
-    EXPECT_TRUE(mObbFile->writeTo(mFileName))
+    EXPECT_TRUE(mObbFile->writeTo(mFileName.string()))
             << "couldn't write to fake .obb file";
 
     mObbFile = new ObbFile();
 
-    EXPECT_TRUE(mObbFile->readFrom(mFileName))
+    EXPECT_TRUE(mObbFile->readFrom(mFileName.string()))
             << "couldn't read from fake .obb file";
 
     EXPECT_EQ(versionNum, mObbFile->getVersion())
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
new file mode 100644
index 0000000..4d07130
--- /dev/null
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/system/R.h"
+#include "data/app/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+#include "data/system/system_arsc.h"
+#include "data/app/app_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+/**
+ * TODO(adamlesinski): Enable when fixed.
+ */
+TEST(ThemeTest, DISABLED_shouldCopyThemeFromDifferentResTable) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(system_arsc, system_arsc_len));
+    ASSERT_EQ(NO_ERROR, table.add(app_arsc, app_arsc_len));
+
+    ResTable::Theme theme1(table);
+    ASSERT_EQ(NO_ERROR, theme1.applyStyle(app::R::style::Theme_One));
+    Res_value val;
+    ASSERT_GE(theme1.getAttribute(android::R::attr::background, &val), 0);
+    ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
+    ASSERT_EQ(uint32_t(0xffff0000), val.data);
+    ASSERT_GE(theme1.getAttribute(app::R::attr::number, &val), 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+    ASSERT_EQ(uint32_t(1), val.data);
+
+    ResTable table2;
+    ASSERT_EQ(NO_ERROR, table2.add(system_arsc, system_arsc_len));
+    ASSERT_EQ(NO_ERROR, table2.add(app_arsc, app_arsc_len));
+
+    ResTable::Theme theme2(table2);
+    ASSERT_EQ(NO_ERROR, theme2.setTo(theme1));
+    ASSERT_GE(theme2.getAttribute(android::R::attr::background, &val), 0);
+    ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
+    ASSERT_EQ(uint32_t(0xffff0000), val.data);
+    ASSERT_GE(theme2.getAttribute(app::R::attr::number, &val), 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+    ASSERT_EQ(uint32_t(1), val.data);
+}
+
+}
diff --git a/libs/androidfw/tests/data/app/AndroidManifest.xml b/libs/androidfw/tests/data/app/AndroidManifest.xml
new file mode 100644
index 0000000..bfa3a39
--- /dev/null
+++ b/libs/androidfw/tests/data/app/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.app">
+</manifest>
diff --git a/libs/androidfw/tests/data/app/R.h b/libs/androidfw/tests/data/app/R.h
new file mode 100644
index 0000000..780a116
--- /dev/null
+++ b/libs/androidfw/tests/data/app/R.h
@@ -0,0 +1,22 @@
+#ifndef __APP_R_H
+#define __APP_R_H
+
+namespace app {
+namespace R {
+
+namespace attr {
+    enum {
+        number         = 0x7f010000,   // default
+    };
+}
+
+namespace style {
+    enum {
+        Theme_One      = 0x7f020000,   // default
+    };
+}
+
+} // namespace R
+} // namespace app
+
+#endif // __APP_R_H
diff --git a/libs/androidfw/tests/data/app/app_arsc.h b/libs/androidfw/tests/data/app/app_arsc.h
new file mode 100644
index 0000000..d5d9a3b
--- /dev/null
+++ b/libs/androidfw/tests/data/app/app_arsc.h
@@ -0,0 +1,62 @@
+unsigned char app_arsc[] = {
+  0x02, 0x00, 0x0c, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x9c, 0x02, 0x00, 0x00,
+  0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+  0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+  0x64, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+  0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
+  0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+  0x4c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6e, 0x00,
+  0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00,
+  0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
+  0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00
+};
+unsigned int app_arsc_len = 708;
diff --git a/libs/androidfw/tests/data/app/build b/libs/androidfw/tests/data/app/build
new file mode 100755
index 0000000..89c4641
--- /dev/null
+++ b/libs/androidfw/tests/data/app/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -v -I ../system/bundle.apk -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc app.arsc && \
+xxd -i app.arsc > app_arsc.h
diff --git a/libs/androidfw/tests/data/app/res/values/values.xml b/libs/androidfw/tests/data/app/res/values/values.xml
new file mode 100644
index 0000000..b0ead38
--- /dev/null
+++ b/libs/androidfw/tests/data/app/res/values/values.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <attr name="number" format="integer"/>
+    <style name="Theme.One" parent="@android:style/Theme.One">
+        <item name="number">1</item>
+    </style>
+</resources>
diff --git a/libs/androidfw/tests/data/system/AndroidManifest.xml b/libs/androidfw/tests/data/system/AndroidManifest.xml
new file mode 100644
index 0000000..af105ee
--- /dev/null
+++ b/libs/androidfw/tests/data/system/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android">
+</manifest>
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
new file mode 100644
index 0000000..7a9d3db
--- /dev/null
+++ b/libs/androidfw/tests/data/system/R.h
@@ -0,0 +1,23 @@
+#ifndef __ANDROID_R_H
+#define __ANDROID_R_H
+
+namespace android {
+namespace R {
+
+namespace attr {
+    enum {
+        background  = 0x01010000, // default
+        foreground  = 0x01010001, // default
+    };
+}
+
+namespace style {
+    enum {
+        Theme_One      = 0x01020000,   // default
+    };
+}
+
+} // namespace R
+} // namespace android
+
+#endif // __ANDROID_R_H
diff --git a/libs/androidfw/tests/data/system/build b/libs/androidfw/tests/data/system/build
new file mode 100755
index 0000000..2a3ac0b
--- /dev/null
+++ b/libs/androidfw/tests/data/system/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -x -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc system.arsc && \
+xxd -i system.arsc > system_arsc.h
diff --git a/libs/androidfw/tests/data/system/res/values/themes.xml b/libs/androidfw/tests/data/system/res/values/themes.xml
new file mode 100644
index 0000000..b29848e
--- /dev/null
+++ b/libs/androidfw/tests/data/system/res/values/themes.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <public name="background" type="attr" id="0x01010000"/>
+    <public name="foreground" type="attr" id="0x01010001"/>
+    <public name="Theme.One" type="style" id="0x01020000"/>
+
+    <attr name="background" format="color|reference"/>
+    <attr name="foreground" format="color|reference"/>
+    <style name="Theme.One" parent="">
+        <item name="android:background">#ff0000</item>
+        <item name="android:foreground">#000000</item>
+    </style>
+</resources>
diff --git a/libs/androidfw/tests/data/system/system_arsc.h b/libs/androidfw/tests/data/system/system_arsc.h
new file mode 100644
index 0000000..215ecae
--- /dev/null
+++ b/libs/androidfw/tests/data/system/system_arsc.h
@@ -0,0 +1,69 @@
+unsigned char system_arsc[] = {
+  0x02, 0x00, 0x0c, 0x00, 0x18, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf0, 0x02, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
+  0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+  0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
+  0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+  0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x67, 0x00,
+  0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x67, 0x00,
+  0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
+  0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
+  0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
+  0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x44, 0x00,
+  0x70, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff,
+  0x01, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff
+};
+unsigned int system_arsc_len = 792;
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 9cc83ed..7834ef8 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -16,6 +16,43 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
+/**
+ * Extra vertices for the corner for smoother corner.
+ * Only for outer vertices.
+ * Note that we use such extra memory to avoid an extra loop.
+ */
+// For half circle, we could add EXTRA_VERTEX_PER_PI vertices.
+// Set to 1 if we don't want to have any.
+#define EXTRA_CORNER_VERTEX_PER_PI 12
+
+// For the whole polygon, the sum of all the deltas b/t normals is 2 * M_PI,
+// therefore, the maximum number of extra vertices will be twice bigger.
+#define MAX_EXTRA_CORNER_VERTEX_NUMBER  (2 * EXTRA_CORNER_VERTEX_PER_PI)
+
+// For each RADIANS_DIVISOR, we would allocate one more vertex b/t the normals.
+#define CORNER_RADIANS_DIVISOR (M_PI / EXTRA_CORNER_VERTEX_PER_PI)
+
+/**
+ * Extra vertices for the Edge for interpolation artifacts.
+ * Same value for both inner and outer vertices.
+ */
+#define EXTRA_EDGE_VERTEX_PER_PI 50
+
+#define MAX_EXTRA_EDGE_VERTEX_NUMBER  (2 * EXTRA_EDGE_VERTEX_PER_PI)
+
+#define EDGE_RADIANS_DIVISOR  (M_PI / EXTRA_EDGE_VERTEX_PER_PI)
+
+/**
+ * Other constants:
+ */
+// For the edge of the penumbra, the opacity is 0.
+#define OUTER_OPACITY (0.0f)
+
+// Once the alpha difference is greater than this threshold, we will allocate extra
+// edge vertices.
+// If this is set to negative value, then all the edge will be tessellated.
+#define ALPHA_THRESHOLD (0.1f / 255.0f)
+
 #include <math.h>
 #include <utils/Log.h>
 #include <utils/Vector.h>
@@ -23,11 +60,97 @@
 #include "AmbientShadow.h"
 #include "ShadowTessellator.h"
 #include "Vertex.h"
+#include "utils/MathUtils.h"
 
 namespace android {
 namespace uirenderer {
 
 /**
+ *  Local utility functions.
+ */
+inline Vector2 getNormalFromVertices(const Vector3* vertices, int current, int next) {
+    // Convert from Vector3 to Vector2 first.
+    Vector2 currentVertex = { vertices[current].x, vertices[current].y };
+    Vector2 nextVertex = { vertices[next].x, vertices[next].y };
+
+    return ShadowTessellator::calculateNormal(currentVertex, nextVertex);
+}
+
+// The input z value will be converted to be non-negative inside.
+// The output must be ranged from 0 to 1.
+inline float getAlphaFromFactoredZ(float factoredZ) {
+    return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f));
+}
+
+inline float getTransformedAlphaFromAlpha(float alpha) {
+    return acosf(1.0f - 2.0f * alpha);
+}
+
+// The output is ranged from 0 to M_PI.
+inline float getTransformedAlphaFromFactoredZ(float factoredZ) {
+    return getTransformedAlphaFromAlpha(getAlphaFromFactoredZ(factoredZ));
+}
+
+inline int getExtraVertexNumber(const Vector2& vector1, const Vector2& vector2,
+        float divisor) {
+    // The formula is :
+    // extraNumber = floor(acos(dot(n1, n2)) / (M_PI / EXTRA_VERTEX_PER_PI))
+    // The value ranges for each step are:
+    // dot( ) --- [-1, 1]
+    // acos( )     --- [0, M_PI]
+    // floor(...)  --- [0, EXTRA_VERTEX_PER_PI]
+    float dotProduct = vector1.dot(vector2);
+    // TODO: Use look up table for the dotProduct to extraVerticesNumber
+    // computation, if needed.
+    float angle = acosf(dotProduct);
+    return (int) floor(angle / divisor);
+}
+
+inline void checkOverflow(int used, int total, const char* bufferName) {
+    LOG_ALWAYS_FATAL_IF(used > total, "Error: %s overflow!!! used %d, total %d",
+            bufferName, used, total);
+}
+
+inline int getEdgeExtraAndUpdateSpike(Vector2* currentSpike,
+        const Vector3& secondVertex, const Vector3& centroid) {
+    Vector2 secondSpike  = {secondVertex.x - centroid.x, secondVertex.y - centroid.y};
+    secondSpike.normalize();
+
+    int result = getExtraVertexNumber(secondSpike, *currentSpike, EDGE_RADIANS_DIVISOR);
+    *currentSpike = secondSpike;
+    return result;
+}
+
+// Given the caster's vertex count, compute all the buffers size depending on
+// whether or not the caster is opaque.
+inline void computeBufferSize(int* totalVertexCount, int* totalIndexCount,
+        int* totalUmbraCount, int casterVertexCount, bool isCasterOpaque) {
+    // Compute the size of the vertex buffer.
+    int outerVertexCount = casterVertexCount * 2 + MAX_EXTRA_CORNER_VERTEX_NUMBER +
+        MAX_EXTRA_EDGE_VERTEX_NUMBER;
+    int innerVertexCount = casterVertexCount + MAX_EXTRA_EDGE_VERTEX_NUMBER;
+    *totalVertexCount = outerVertexCount + innerVertexCount;
+
+    // Compute the size of the index buffer.
+    *totalIndexCount = 2 * outerVertexCount + 2;
+
+    // Compute the size of the umber buffer.
+    // For translucent object, keep track of the umbra(inner) vertex in order to draw
+    // inside. We only need to store the index information.
+    *totalUmbraCount = 0;
+    if (!isCasterOpaque) {
+        // Add the centroid if occluder is translucent.
+        *totalVertexCount++;
+        *totalIndexCount += 2 * innerVertexCount + 1;
+        *totalUmbraCount = innerVertexCount;
+    }
+}
+
+inline bool needsExtraForEdge(float firstAlpha, float secondAlpha) {
+    return abs(firstAlpha - secondAlpha) > ALPHA_THRESHOLD;
+}
+
+/**
  * Calculate the shadows as a triangle strips while alpha value as the
  * shadow values.
  *
@@ -43,290 +166,198 @@
  *
  * @param shadowVertexBuffer Return an floating point array of (x, y, a)
  *               triangle strips mode.
+ *
+ * An simple illustration:
+ * For now let's mark the outer vertex as Pi, the inner as Vi, the centroid as C.
+ *
+ * First project the occluder to the Z=0 surface.
+ * Then we got all the inner vertices. And we compute the normal for each edge.
+ * According to the normal, we generate outer vertices. E.g: We generate P1 / P4
+ * as extra corner vertices to make the corner looks round and smoother.
+ *
+ * Due to the fact that the alpha is not linear interpolated along the inner
+ * edge, when the alpha is different, we may add extra vertices such as P2.1, P2.2,
+ * V0.1, V0.2 to avoid the visual artifacts.
+ *
+ *                                            (P3)
+ *          (P2)     (P2.1)     (P2.2)         |     ' (P4)
+ *   (P1)'   |        |           |            |   '
+ *         ' |        |           |            | '
+ * (P0)  ------------------------------------------------(P5)
+ *           | (V0)   (V0.1)    (V0.2)         |(V1)
+ *           |                                 |
+ *           |                                 |
+ *           |               (C)               |
+ *           |                                 |
+ *           |                                 |
+ *           |                                 |
+ *           |                                 |
+ *        (V3)-----------------------------------(V2)
  */
 void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
-        const Vector3* vertices, int vertexCount, const Vector3& centroid3d,
+        const Vector3* casterVertices, int casterVertexCount, const Vector3& centroid3d,
         float heightFactor, float geomFactor, VertexBuffer& shadowVertexBuffer) {
-    const int rays = SHADOW_RAY_COUNT;
-    // Validate the inputs.
-    if (vertexCount < 3 || heightFactor <= 0 || rays <= 0
-        || geomFactor <= 0) {
-#if DEBUG_SHADOW
-        ALOGW("Invalid input for createAmbientShadow(), early return!");
-#endif
-        return;
-    }
+    shadowVertexBuffer.setMode(VertexBuffer::kIndices);
 
-    Vector<Vector2> dir; // TODO: use C++11 unique_ptr
-    dir.setCapacity(rays);
-    float rayDist[rays];
-    float rayHeight[rays];
-    calculateRayDirections(rays, vertices, vertexCount, centroid3d, dir.editArray());
+    // In order to computer the outer vertices in one loop, we need pre-compute
+    // the normal by the vertex (n - 1) to vertex 0, and the spike and alpha value
+    // for vertex 0.
+    Vector2 previousNormal = getNormalFromVertices(casterVertices,
+            casterVertexCount - 1 , 0);
+    Vector2 currentSpike = {casterVertices[0].x - centroid3d.x,
+        casterVertices[0].y - centroid3d.y};
+    currentSpike.normalize();
+    float currentAlpha = getAlphaFromFactoredZ(casterVertices[0].z * heightFactor);
 
-    // Calculate the length and height of the points along the edge.
-    //
-    // The math here is:
-    // Intersect each ray (starting from the centroid) with the polygon.
-    for (int i = 0; i < rays; i++) {
-        int edgeIndex;
-        float edgeFraction;
-        float rayDistance;
-        calculateIntersection(vertices, vertexCount, centroid3d, dir[i], edgeIndex,
-                edgeFraction, rayDistance);
-        rayDist[i] = rayDistance;
-        if (edgeIndex < 0 || edgeIndex >= vertexCount) {
-#if DEBUG_SHADOW
-            ALOGW("Invalid edgeIndex!");
-#endif
-            edgeIndex = 0;
-        }
-        float h1 = vertices[edgeIndex].z;
-        float h2 = vertices[((edgeIndex + 1) % vertexCount)].z;
-        rayHeight[i] = h1 + edgeFraction * (h2 - h1);
-    }
-
-    // The output buffer length basically is roughly rays * layers, but since we
-    // need triangle strips, so we need to duplicate vertices to accomplish that.
+    // Preparing all the output data.
+    int totalVertexCount, totalIndexCount, totalUmbraCount;
+    computeBufferSize(&totalVertexCount, &totalIndexCount, &totalUmbraCount,
+            casterVertexCount, isCasterOpaque);
     AlphaVertex* shadowVertices =
-            shadowVertexBuffer.alloc<AlphaVertex>(SHADOW_VERTEX_COUNT);
+            shadowVertexBuffer.alloc<AlphaVertex>(totalVertexCount);
+    int vertexBufferIndex = 0;
+    uint16_t* indexBuffer = shadowVertexBuffer.allocIndices<uint16_t>(totalIndexCount);
+    int indexBufferIndex = 0;
+    uint16_t umbraVertices[totalUmbraCount];
+    int umbraIndex = 0;
 
-    // Calculate the vertex of the shadows.
-    //
-    // The math here is:
-    // Along the edges of the polygon, for each intersection point P (generated above),
-    // calculate the normal N, which should be perpendicular to the edge of the
-    // polygon (represented by the neighbor intersection points) .
-    // Shadow's vertices will be generated as : P + N * scale.
-    const Vector2 centroid2d = {centroid3d.x, centroid3d.y};
-    for (int rayIndex = 0; rayIndex < rays; rayIndex++) {
-        Vector2 normal = {1.0f, 0.0f};
-        calculateNormal(rays, rayIndex, dir.array(), rayDist, normal);
+    for (int i = 0; i < casterVertexCount; i++)  {
+        // Corner: first figure out the extra vertices we need for the corner.
+        const Vector3& innerVertex = casterVertices[i];
+        Vector2 currentNormal = getNormalFromVertices(casterVertices, i,
+                (i + 1) % casterVertexCount);
 
-        // The vertex should be start from rayDist[i] then scale the
-        // normalizeNormal!
-        Vector2 intersection = dir[rayIndex] * rayDist[rayIndex] +
-                centroid2d;
+        int extraVerticesNumber = getExtraVertexNumber(currentNormal, previousNormal,
+                CORNER_RADIANS_DIVISOR);
 
-        // outer ring of points, expanded based upon height of each ray intersection
-        float expansionDist = rayHeight[rayIndex] * heightFactor *
-                geomFactor;
-        AlphaVertex::set(&shadowVertices[rayIndex],
-                intersection.x + normal.x * expansionDist,
-                intersection.y + normal.y * expansionDist,
-                0.0f);
-
-        // inner ring of points
-        float opacity = 1.0 / (1 + rayHeight[rayIndex] * heightFactor);
-        // NOTE: Shadow alpha values are transformed when stored in alphavertices,
-        // so that they can be consumed directly by gFS_Main_ApplyVertexAlphaShadowInterp
-        float transformedOpacity = acos(1.0f - 2.0f * opacity);
-        AlphaVertex::set(&shadowVertices[rays + rayIndex],
-                intersection.x,
-                intersection.y,
-                transformedOpacity);
-    }
-
-    if (isCasterOpaque) {
-        // skip inner ring, calc bounds over filled portion of buffer
-        shadowVertexBuffer.computeBounds<AlphaVertex>(2 * rays);
-        shadowVertexBuffer.setMode(VertexBuffer::kOnePolyRingShadow);
-    } else {
-        // If caster isn't opaque, we need to to fill the umbra by storing the umbra's
-        // centroid in the innermost ring of vertices.
-        float centroidAlpha = 1.0 / (1 + centroid3d.z * heightFactor);
-        AlphaVertex centroidXYA;
-        AlphaVertex::set(&centroidXYA, centroid2d.x, centroid2d.y, centroidAlpha);
-        for (int rayIndex = 0; rayIndex < rays; rayIndex++) {
-            shadowVertices[2 * rays + rayIndex] = centroidXYA;
-        }
-        // calc bounds over entire buffer
-        shadowVertexBuffer.computeBounds<AlphaVertex>();
-        shadowVertexBuffer.setMode(VertexBuffer::kTwoPolyRingShadow);
-    }
-
+        float expansionDist = innerVertex.z * heightFactor * geomFactor;
+        const int cornerSlicesNumber = extraVerticesNumber + 1; // Minimal as 1.
 #if DEBUG_SHADOW
-    for (int i = 0; i < SHADOW_VERTEX_COUNT; i++) {
-        ALOGD("ambient shadow value: i %d, (x:%f, y:%f, a:%f)", i, shadowVertices[i].x,
-                shadowVertices[i].y, shadowVertices[i].alpha);
-    }
+        ALOGD("cornerSlicesNumber is %d", cornerSlicesNumber);
 #endif
-}
 
-/**
- * Generate an array of rays' direction vectors.
- * To make sure the vertices generated are clockwise, the directions are from PI
- * to -PI.
- *
- * @param rays The number of rays shooting out from the centroid.
- * @param vertices Vertices of the polygon.
- * @param vertexCount The number of vertices.
- * @param centroid3d The centroid of the polygon.
- * @param dir Return the array of ray vectors.
- */
-void AmbientShadow::calculateRayDirections(const int rays, const Vector3* vertices,
-        const int vertexCount, const Vector3& centroid3d, Vector2* dir) {
-    // If we don't have enough rays, then fall back to the uniform distribution.
-    if (vertexCount * 2 > rays) {
-        float deltaAngle = 2 * M_PI / rays;
-        for (int i = 0; i < rays; i++) {
-            dir[i].x = cosf(M_PI - deltaAngle * i);
-            dir[i].y = sinf(M_PI - deltaAngle * i);
+        // Corner: fill the corner Vertex Buffer(VB) and Index Buffer(IB).
+        // We fill the inner vertex first, such that we can fill the index buffer
+        // inside the loop.
+        int currentInnerVertexIndex = vertexBufferIndex;
+        if (!isCasterOpaque) {
+            umbraVertices[umbraIndex++] = vertexBufferIndex;
         }
-        return;
-    }
+        AlphaVertex::set(&shadowVertices[vertexBufferIndex++], casterVertices[i].x,
+                casterVertices[i].y,
+                getTransformedAlphaFromAlpha(currentAlpha));
 
-    // If we have enough rays, then we assign each vertices a ray, and distribute
-    // the rest uniformly.
-    float rayThetas[rays];
+        const Vector3& innerStart = casterVertices[i];
 
-    const int uniformRayCount = rays - vertexCount;
-    const float deltaAngle = 2 * M_PI / uniformRayCount;
+        // outerStart is the first outer vertex for this inner vertex.
+        // outerLast is the last outer vertex for this inner vertex.
+        Vector2 outerStart = {0, 0};
+        Vector2 outerLast = {0, 0};
+        // This will create vertices from [0, cornerSlicesNumber] inclusively,
+        // which means minimally 2 vertices even without the extra ones.
+        for (int j = 0; j <= cornerSlicesNumber; j++) {
+            Vector2 averageNormal =
+                previousNormal * (cornerSlicesNumber - j) + currentNormal * j;
+            averageNormal /= cornerSlicesNumber;
+            averageNormal.normalize();
+            Vector2 outerVertex;
+            outerVertex.x = innerVertex.x + averageNormal.x * expansionDist;
+            outerVertex.y = innerVertex.y + averageNormal.y * expansionDist;
 
-    // We have to generate all the vertices' theta anyway and we also need to
-    // find the minimal, so let's precompute it first.
-    // Since the incoming polygon is clockwise, we can find the dip to identify
-    // the minimal theta.
-    float polyThetas[vertexCount];
-    int maxPolyThetaIndex = 0;
-    for (int i = 0; i < vertexCount; i++) {
-        polyThetas[i] = atan2(vertices[i].y - centroid3d.y,
-                vertices[i].x - centroid3d.x);
-        if (i > 0 && polyThetas[i] > polyThetas[i - 1]) {
-            maxPolyThetaIndex = i;
-        }
-    }
+            indexBuffer[indexBufferIndex++] = vertexBufferIndex;
+            indexBuffer[indexBufferIndex++] = currentInnerVertexIndex;
+            AlphaVertex::set(&shadowVertices[vertexBufferIndex++], outerVertex.x,
+                    outerVertex.y, OUTER_OPACITY);
 
-    // Both poly's thetas and uniform thetas are in decrease order(clockwise)
-    // from PI to -PI.
-    int polyThetaIndex = maxPolyThetaIndex;
-    float polyTheta = polyThetas[maxPolyThetaIndex];
-    int uniformThetaIndex = 0;
-    float uniformTheta = M_PI;
-    for (int i = 0; i < rays; i++) {
-        // Compare both thetas and pick the smaller one and move on.
-        bool hasThetaCollision = abs(polyTheta - uniformTheta) < MINIMAL_DELTA_THETA;
-        if (polyTheta > uniformTheta || hasThetaCollision) {
-            if (hasThetaCollision) {
-                // Shift the uniformTheta to middle way between current polyTheta
-                // and next uniform theta. The next uniform theta can wrap around
-                // to exactly PI safely here.
-                // Note that neither polyTheta nor uniformTheta can be FLT_MAX
-                // due to the hasThetaCollision is true.
-                uniformTheta = (polyTheta +  M_PI - deltaAngle * (uniformThetaIndex + 1)) / 2;
-#if DEBUG_SHADOW
-                ALOGD("Shifted uniformTheta to %f", uniformTheta);
-#endif
-            }
-            rayThetas[i] = polyTheta;
-            polyThetaIndex = (polyThetaIndex + 1) % vertexCount;
-            if (polyThetaIndex != maxPolyThetaIndex) {
-                polyTheta = polyThetas[polyThetaIndex];
-            } else {
-                // out of poly points.
-                polyTheta = - FLT_MAX;
-            }
-        } else {
-            rayThetas[i] = uniformTheta;
-            uniformThetaIndex++;
-            if (uniformThetaIndex < uniformRayCount) {
-                uniformTheta = M_PI - deltaAngle * uniformThetaIndex;
-            } else {
-                // out of uniform points.
-                uniformTheta = - FLT_MAX;
+            if (j == 0) {
+                outerStart = outerVertex;
+            } else if (j == cornerSlicesNumber) {
+                outerLast = outerVertex;
             }
         }
-    }
+        previousNormal = currentNormal;
 
-    for (int i = 0; i < rays; i++) {
+        // Edge: first figure out the extra vertices needed for the edge.
+        const Vector3& innerNext = casterVertices[(i + 1) % casterVertexCount];
+        float nextAlpha = getAlphaFromFactoredZ(innerNext.z * heightFactor);
+        if (needsExtraForEdge(currentAlpha, nextAlpha)) {
+            // TODO: See if we can / should cache this outer vertex across the loop.
+            Vector2 outerNext;
+            float expansionDist = innerNext.z * heightFactor * geomFactor;
+            outerNext.x = innerNext.x + currentNormal.x * expansionDist;
+            outerNext.y = innerNext.y + currentNormal.y * expansionDist;
+
+            // Compute the angle and see how many extra points we need.
+            int extraVerticesNumber = getEdgeExtraAndUpdateSpike(&currentSpike,
+                    innerNext, centroid3d);
 #if DEBUG_SHADOW
-        ALOGD("No. %d : %f", i, rayThetas[i] * 180 / M_PI);
+            ALOGD("extraVerticesNumber %d for edge %d", extraVerticesNumber, i);
 #endif
-        // TODO: Fix the intersection precision problem and remvoe the delta added
-        // here.
-        dir[i].x = cosf(rayThetas[i] + MINIMAL_DELTA_THETA);
-        dir[i].y = sinf(rayThetas[i] + MINIMAL_DELTA_THETA);
-    }
-}
+            // Edge: fill the edge's VB and IB.
+            // This will create vertices pair from [1, extraVerticesNumber - 1].
+            // If there is no extra vertices created here, the edge will be drawn
+            // as just 2 triangles.
+            for (int k = 1; k < extraVerticesNumber; k++) {
+                int startWeight = extraVerticesNumber - k;
+                Vector2 currentOuter =
+                    (outerLast * startWeight + outerNext * k) / extraVerticesNumber;
+                indexBuffer[indexBufferIndex++] = vertexBufferIndex;
+                AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentOuter.x,
+                        currentOuter.y, OUTER_OPACITY);
 
-/**
- * Calculate the intersection of a ray hitting the polygon.
- *
- * @param vertices The shadow caster's polygon, which is represented in a
- *                 Vector3 array.
- * @param vertexCount The length of caster's polygon in terms of number of vertices.
- * @param start The starting point of the ray.
- * @param dir The direction vector of the ray.
- *
- * @param outEdgeIndex Return the index of the segment (or index of the starting
- *                     vertex) that ray intersect with.
- * @param outEdgeFraction Return the fraction offset from the segment starting
- *                        index.
- * @param outRayDist Return the ray distance from centroid to the intersection.
- */
-void AmbientShadow::calculateIntersection(const Vector3* vertices, int vertexCount,
-        const Vector3& start, const Vector2& dir, int& outEdgeIndex,
-        float& outEdgeFraction, float& outRayDist) {
-    float startX = start.x;
-    float startY = start.y;
-    float dirX = dir.x;
-    float dirY = dir.y;
-    // Start the search from the last edge from poly[len-1] to poly[0].
-    int p1 = vertexCount - 1;
-
-    for (int p2 = 0; p2 < vertexCount; p2++) {
-        float p1x = vertices[p1].x;
-        float p1y = vertices[p1].y;
-        float p2x = vertices[p2].x;
-        float p2y = vertices[p2].y;
-
-        // The math here is derived from:
-        // f(t, v) = p1x * (1 - t) + p2x * t - (startX + dirX * v) = 0;
-        // g(t, v) = p1y * (1 - t) + p2y * t - (startY + dirY * v) = 0;
-        float div = (dirX * (p1y - p2y) + dirY * p2x - dirY * p1x);
-        if (div != 0) {
-            float t = (dirX * (p1y - startY) + dirY * startX - dirY * p1x) / (div);
-            if (t > 0 && t <= 1) {
-                float t2 = (p1x * (startY - p2y)
-                            + p2x * (p1y - startY)
-                            + startX * (p2y - p1y)) / div;
-                if (t2 > 0) {
-                    outEdgeIndex = p1;
-                    outRayDist = t2;
-                    outEdgeFraction = t;
-                    return;
+                if (!isCasterOpaque) {
+                    umbraVertices[umbraIndex++] = vertexBufferIndex;
                 }
+                Vector3 currentInner =
+                    (innerStart * startWeight + innerNext * k) / extraVerticesNumber;
+                indexBuffer[indexBufferIndex++] = vertexBufferIndex;
+                AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentInner.x,
+                        currentInner.y,
+                        getTransformedAlphaFromFactoredZ(currentInner.z * heightFactor));
             }
         }
-        p1 = p2;
+        currentAlpha = nextAlpha;
     }
-    return;
-};
 
-/**
- * Calculate the normal at the intersection point between a ray and the polygon.
- *
- * @param rays The total number of rays.
- * @param currentRayIndex The index of the ray which the normal is based on.
- * @param dir The array of the all the rays directions.
- * @param rayDist The pre-computed ray distances array.
- *
- * @param normal Return the normal.
- */
-void AmbientShadow::calculateNormal(int rays, int currentRayIndex,
-        const Vector2* dir, const float* rayDist, Vector2& normal) {
-    int preIndex = (currentRayIndex - 1 + rays) % rays;
-    int postIndex = (currentRayIndex + 1) % rays;
-    Vector2 p1 = dir[preIndex] * rayDist[preIndex];
-    Vector2 p2 = dir[postIndex] * rayDist[postIndex];
+    indexBuffer[indexBufferIndex++] = 1;
+    indexBuffer[indexBufferIndex++] = 0;
 
-    // Now the rays are going CW around the poly.
-    Vector2 delta = p2 - p1;
-    if (delta.length() != 0) {
-        delta.normalize();
-        // Calculate the normal , which is CCW 90 rotate to the delta.
-        normal.x = - delta.y;
-        normal.y = delta.x;
+    if (!isCasterOpaque) {
+        // Add the centroid as the last one in the vertex buffer.
+        float centroidOpacity =
+            getTransformedAlphaFromFactoredZ(centroid3d.z * heightFactor);
+        int centroidIndex = vertexBufferIndex;
+        AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid3d.x,
+                centroid3d.y, centroidOpacity);
+
+        for (int i = 0; i < umbraIndex; i++) {
+            // Note that umbraVertices[0] is always 0.
+            // So the start and the end of the umbra are using the "0".
+            // And penumbra ended with 0, so a degenerated triangle is formed b/t
+            // the umbra and penumbra.
+            indexBuffer[indexBufferIndex++] = umbraVertices[i];
+            indexBuffer[indexBufferIndex++] = centroidIndex;
+        }
+        indexBuffer[indexBufferIndex++] = 0;
     }
+
+    // At the end, update the real index and vertex buffer size.
+    shadowVertexBuffer.updateVertexCount(vertexBufferIndex);
+    shadowVertexBuffer.updateIndexCount(indexBufferIndex);
+
+    checkOverflow(vertexBufferIndex, totalVertexCount, "Vertex Buffer");
+    checkOverflow(indexBufferIndex, totalIndexCount, "Index Buffer");
+    checkOverflow(umbraIndex, totalUmbraCount, "Umbra Buffer");
+
+#if DEBUG_SHADOW
+    for (int i = 0; i < vertexBufferIndex; i++) {
+        ALOGD("vertexBuffer i %d, (%f, %f %f)", i, shadowVertices[i].x, shadowVertices[i].y,
+                shadowVertices[i].alpha);
+    }
+    for (int i = 0; i < indexBufferIndex; i++) {
+        ALOGD("indexBuffer i %d, indexBuffer[i] %d", i, indexBuffer[i]);
+    }
+#endif
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/AmbientShadow.h b/libs/hwui/AmbientShadow.h
index 68df246..9660dc0 100644
--- a/libs/hwui/AmbientShadow.h
+++ b/libs/hwui/AmbientShadow.h
@@ -28,27 +28,12 @@
 
 /**
  * AmbientShadow is used to calculate the ambient shadow value around a polygon.
- *
- * TODO: calculateIntersection() now is O(N*M), where N is the number of
- * polygon's vertics and M is the number of rays. In fact, by staring tracing
- * the vertex from the previous intersection, the algorithm can be O(N + M);
  */
 class AmbientShadow {
 public:
     static void createAmbientShadow(bool isCasterOpaque, const Vector3* poly,
             int polyLength, const Vector3& centroid3d, float heightFactor,
             float geomFactor, VertexBuffer& shadowVertexBuffer);
-
-private:
-    static void calculateRayDirections(const int rays, const Vector3* vertices,
-            const int vertexCount, const Vector3& centroid3d, Vector2* dir);
-
-    static void calculateIntersection(const Vector3* poly, int nbVertices,
-            const Vector3& start, const Vector2& dir, int& outEdgeIndex,
-            float& outEdgeFraction, float& outRayDist);
-
-    static void calculateNormal(int rays, int currentRayIndex, const Vector2* dir,
-            const float* rayDist, Vector2& normal);
 }; // AmbientShadow
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 1c697d5..da65f38 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -95,6 +95,8 @@
         // Oh boy, we're starting! Man the battle stations!
         if (mPlayState == RUNNING) {
             transitionToRunning(context);
+        } else if (mPlayState == FINISHED) {
+            callOnFinishedListener(context);
         }
     }
 }
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bbf0551..0f36c06 100755
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2417,6 +2417,10 @@
     } else if (mode == VertexBuffer::kTwoPolyRingShadow) {
         mCaches.bindShadowIndicesBuffer();
         glDrawElements(GL_TRIANGLE_STRIP, TWO_POLY_RING_SHADOW_INDEX_COUNT, GL_UNSIGNED_SHORT, 0);
+    } else if (mode == VertexBuffer::kIndices) {
+        mCaches.unbindIndicesBuffer();
+        glDrawElements(GL_TRIANGLE_STRIP, vertexBuffer.getIndexCount(), GL_UNSIGNED_SHORT,
+                vertexBuffer.getIndices());
     }
 
     if (isAA) {
diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h
index 2a9f01c..d033ed9 100644
--- a/libs/hwui/Vector.h
+++ b/libs/hwui/Vector.h
@@ -111,6 +111,23 @@
     float y;
     float z;
 
+    Vector3 operator+(const Vector3& v) const {
+        return (Vector3){x + v.x, y + v.y, z + v.z};
+    }
+
+    Vector3 operator-(const Vector3& v) const {
+        return (Vector3){x - v.x, y - v.y, z - v.z};
+    }
+
+    Vector3 operator/(float s) const {
+        return (Vector3){x / s, y / s, z / s};
+    }
+
+    Vector3 operator*(float s) const {
+        return (Vector3){x * s, y * s, z * s};
+    }
+
+
     void dump() {
         ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z);
     }
diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h
index 3837f88..966fa4e 100644
--- a/libs/hwui/VertexBuffer.h
+++ b/libs/hwui/VertexBuffer.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_HWUI_VERTEX_BUFFER_H
 #define ANDROID_HWUI_VERTEX_BUFFER_H
 
+#include "utils/MathUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -26,19 +27,27 @@
     enum Mode {
         kStandard = 0,
         kOnePolyRingShadow = 1,
-        kTwoPolyRingShadow = 2
+        kTwoPolyRingShadow = 2,
+        kIndices = 3
     };
 
     VertexBuffer()
             : mBuffer(0)
+            , mIndices(0)
             , mVertexCount(0)
+            , mIndexCount(0)
+            , mAllocatedVertexCount(0)
+            , mAllocatedIndexCount(0)
             , mByteCount(0)
             , mMode(kStandard)
+            , mReallocBuffer(0)
             , mCleanupMethod(NULL)
+            , mCleanupIndexMethod(NULL)
     {}
 
     ~VertexBuffer() {
         if (mCleanupMethod) mCleanupMethod(mBuffer);
+        if (mCleanupIndexMethod) mCleanupIndexMethod(mIndices);
     }
 
     /**
@@ -59,6 +68,7 @@
             mReallocBuffer = reallocBuffer + vertexCount;
             return reallocBuffer;
         }
+        mAllocatedVertexCount = vertexCount;
         mVertexCount = vertexCount;
         mByteCount = mVertexCount * sizeof(TYPE);
         mReallocBuffer = mBuffer = (void*)new TYPE[vertexCount];
@@ -69,6 +79,17 @@
     }
 
     template <class TYPE>
+    TYPE* allocIndices(int indexCount) {
+        mAllocatedIndexCount = indexCount;
+        mIndexCount = indexCount;
+        mIndices = (void*)new TYPE[indexCount];
+
+        mCleanupIndexMethod = &(cleanup<TYPE>);
+
+        return (TYPE*)mIndices;
+    }
+
+    template <class TYPE>
     void copyInto(const VertexBuffer& srcBuffer, float xOffset, float yOffset) {
         int verticesToCopy = srcBuffer.getVertexCount();
 
@@ -103,9 +124,17 @@
     }
 
     const void* getBuffer() const { return mBuffer; }
+    const void* getIndices() const { return mIndices; }
     const Rect& getBounds() const { return mBounds; }
     unsigned int getVertexCount() const { return mVertexCount; }
     unsigned int getSize() const { return mByteCount; }
+    unsigned int getIndexCount() const { return mIndexCount; }
+    void updateIndexCount(unsigned int newCount)  {
+        mIndexCount = MathUtils::min(newCount, mAllocatedIndexCount);
+    }
+    void updateVertexCount(unsigned int newCount)  {
+        newCount = MathUtils::min(newCount, mAllocatedVertexCount);
+    }
     Mode getMode() const { return mMode; }
 
     void setBounds(Rect bounds) { mBounds = bounds; }
@@ -127,14 +156,22 @@
     }
 
     Rect mBounds;
+
     void* mBuffer;
+    void* mIndices;
+
     unsigned int mVertexCount;
+    unsigned int mIndexCount;
+    unsigned int mAllocatedVertexCount;
+    unsigned int mAllocatedIndexCount;
     unsigned int mByteCount;
+
     Mode mMode;
 
     void* mReallocBuffer; // used for multi-allocation
 
     void (*mCleanupMethod)(void*);
+    void (*mCleanupIndexMethod)(void*);
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ecfedf6..491a295 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -314,6 +314,7 @@
     stopDrawing();
     if (mEglManager.hasEglContext()) {
         requireGlContext();
+        freePrefetechedLayers();
         mRootRenderNode->destroyHardwareResources();
         Caches::getInstance().flush(Caches::kFlushMode_Layers);
     }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 711831b..ae8ce4b 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -683,6 +683,7 @@
          *
          * @param mediaButtonIntent an intent containing the KeyEvent as an
          *            extra
+         * @return True if the event was handled, false otherwise.
          */
         public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
             if (mSession != null
@@ -695,36 +696,43 @@
                         case KeyEvent.KEYCODE_MEDIA_PLAY:
                             if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
                                 onPlay();
+                                return true;
                             }
                             break;
                         case KeyEvent.KEYCODE_MEDIA_PAUSE:
                             if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
                                 onPause();
+                                return true;
                             }
                             break;
                         case KeyEvent.KEYCODE_MEDIA_NEXT:
                             if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
                                 onSkipToNext();
+                                return true;
                             }
                             break;
                         case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
                             if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
                                 onSkipToPrevious();
+                                return true;
                             }
                             break;
                         case KeyEvent.KEYCODE_MEDIA_STOP:
                             if ((validActions & PlaybackState.ACTION_STOP) != 0) {
                                 onStop();
+                                return true;
                             }
                             break;
                         case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
                             if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
                                 onFastForward();
+                                return true;
                             }
                             break;
                         case KeyEvent.KEYCODE_MEDIA_REWIND:
                             if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
                                 onRewind();
+                                return true;
                             }
                             break;
                         case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@@ -737,8 +745,10 @@
                                     | PlaybackState.ACTION_PAUSE)) != 0;
                             if (isPlaying && canPause) {
                                 onPause();
+                                return true;
                             } else if (!isPlaying && canPlay) {
                                 onPlay();
+                                return true;
                             }
                             break;
                     }
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 0bda2b3..ccc6ad8 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -136,7 +136,7 @@
  * <table>
  *     <tr>
  *         <th>Constant Value</th>
- *         <th>Comment</th>
+ *         <th>Description</th>
  *     </tr>
  *     <tr>
  *         <td>com.android.tv</td>
@@ -148,7 +148,7 @@
  * <table>
  *     <tr>
  *         <th>Constant Value</th>
- *         <th>Comment</th>
+ *         <th>Description</th>
  *     </tr>
  *     <tr>
  *         <td>AM_TV_RS</td>
@@ -346,7 +346,7 @@
  *     <tr>
  *         <th>Rating System</th>
  *         <th>Constant Value</th>
- *         <th>Comment</th>
+ *         <th>Description</th>
  *     </tr>
  *     <tr>
  *         <td valign="top" rowspan="6">AM_TV_RS</td>
@@ -1419,7 +1419,7 @@
  *     <tr>
  *         <th>Rating System</th>
  *         <th>Constant Value</th>
- *         <th>Comment</th>
+ *         <th>Description</th>
  *     </tr>
  *     <tr>
  *         <td valign="top" rowspan="6">NL_TV</td>
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 00c7ad4..7f1c304 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -680,7 +680,7 @@
          * <p>
          * A value of 1 indicates the channel is included in the channel list that applications use
          * to browse channels, a value of 0 indicates the channel is not included in the list. If
-         * not specified, this value is set to 1 (browsable) by default.
+         * not specified, this value is set to 0 (not browsable) by default.
          * </p><p>
          * Type: INTEGER (boolean)
          * </p>
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index c678ac7..0fed27e 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -263,7 +263,7 @@
 
     String8 vendorMessage;
     if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
-        vendorMessage.format("DRM vendor-defined error: %d", err);
+        vendorMessage = String8::format("DRM vendor-defined error: %d", err);
         drmMessage = vendorMessage.string();
     }
 
@@ -285,7 +285,7 @@
             if (msg == NULL) {
                 msg = drmMessage;
             } else {
-                errbuf.format("%s: %s", msg, drmMessage);
+                errbuf = String8::format("%s: %s", msg, drmMessage);
                 msg = errbuf.string();
             }
         }
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index ee5d42a..761d58a 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -27,6 +27,7 @@
         android:id="@+id/static_content"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
+        android:paddingStart="8dip"
         android:elevation="@dimen/preview_controls_elevation"
         android:background="?android:attr/colorPrimary">
 
diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml
index 0629481..c2a0da9 100644
--- a/packages/PrintSpooler/res/layout/print_activity_controls.xml
+++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml
@@ -55,8 +55,7 @@
                     android:text="@string/label_copies">
                 </TextView>
 
-                <view
-                    class="com.android.printspooler.widget.FirstFocusableEditText"
+                <EditText
                     android:id="@+id/copies_edittext"
                     android:layout_width="fill_parent"
                     android:layout_height="wrap_content"
@@ -64,7 +63,7 @@
                     android:singleLine="true"
                     android:ellipsize="end"
                     android:inputType="numberDecimal">
-                </view>
+                </EditText>
 
             </LinearLayout>
 
@@ -198,8 +197,7 @@
                     android:visibility="visible">
                 </TextView>
 
-                <view
-                    class="com.android.printspooler.widget.FirstFocusableEditText"
+                <EditText
                     android:id="@+id/page_range_edittext"
                     android:layout_width="fill_parent"
                     android:layout_height="wrap_content"
@@ -208,7 +206,7 @@
                     android:ellipsize="end"
                     android:visibility="visible"
                     android:inputType="textNoSuggestions">
-                </view>
+                </EditText>
 
             </LinearLayout>
 
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
index 43d8aaf..4381a7a 100644
--- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -17,8 +17,8 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
-      android:paddingStart="16dip"
-      android:paddingEnd="16dip"
+      android:paddingStart="8dip"
+      android:paddingEnd="8dip"
       android:minHeight="56dip"
       android:orientation="horizontal"
       android:gravity="start|center_vertical">
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
index 173057b..77c500a 100644
--- a/packages/PrintSpooler/res/layout/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -23,8 +23,6 @@
         android:id="@android:id/list"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
-        android:paddingStart="@dimen/printer_list_view_padding_start"
-        android:paddingEnd="@dimen/printer_list_view_padding_end"
         android:scrollbarStyle="outsideOverlay"
         android:cacheColorHint="@android:color/transparent"
         android:scrollbarAlwaysDrawVerticalTrack="true" >
diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
deleted file mode 100644
index 14403a1..0000000
--- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeightSmall"
-    android:orientation="vertical"
-    android:gravity="start|center_vertical">
-
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="?android:attr/spinnerDropDownItemStyle"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorPrimary"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:textIsSelectable="false"
-        android:gravity="top|left"
-        android:duplicateParentState="true">
-    </TextView>
-
-    <TextView
-        android:id="@+id/subtitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="?android:attr/spinnerDropDownItemStyle"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorPrimary"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:textIsSelectable="false"
-        android:visibility="gone"
-        android:duplicateParentState="true">
-    </TextView>
-
-</LinearLayout>
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
index a4666a5..6cf9754b5 100644
--- a/packages/PrintSpooler/res/values-land/constants.xml
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -16,10 +16,6 @@
 
 <resources>
 
-    <dimen name="printer_list_view_padding_start">48dip</dimen>
-
-    <dimen name="printer_list_view_padding_end">48dip</dimen>
-
     <integer name="preview_page_per_row_count">2</integer>
 
     <integer name="print_option_column_count">3</integer>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index b95703b..b4e4777 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -28,9 +28,6 @@
 
     <dimen name="print_dialog_frame_max_width_dip">400dip</dimen>
 
-    <dimen name="printer_list_view_padding_start">16dip</dimen>
-    <dimen name="printer_list_view_padding_end">16dip</dimen>
-
     <dimen name="selected_page_elevation">6dip</dimen>
     <dimen name="unselected_page_elevation">2dip</dimen>
 
@@ -46,6 +43,6 @@
     <fraction name="page_unselected_alpha">50%</fraction>
 
     <dimen name="preview_page_footer_height">32dip</dimen>
-    <dimen name="preview_page_min_width">130dip</dimen>
+    <dimen name="preview_page_min_width">128dip</dimen>
 
 </resources>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index db319e9..532b01f 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -16,7 +16,10 @@
 
 <resources>
 
-    <style name="PrintActivity" parent="@android:style/Theme.Material.Settings">
+    <style name="PrintActivity" parent="@android:style/Theme.Material">
+        <item name="android:colorPrimary">@*android:color/material_blue_grey_900</item>
+        <item name="android:colorPrimaryDark">@*android:color/material_blue_grey_950</item>
+        <item name="android:colorAccent">@*android:color/material_deep_teal_500</item>
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowContentOverlay">@null</item>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 5bcdb9f..d949673 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -289,15 +289,11 @@
                     + " for position: " + position);
         }
 
-        final int pageCount = getItemCount();
         MyViewHolder myHolder = (MyViewHolder) holder;
 
         View page = holder.itemView;
-        if (pageCount > 1) {
-            page.setOnClickListener(mPageClickListener);
-        } else {
-            page.setOnClickListener(null);
-        }
+        page.setOnClickListener(mPageClickListener);
+
         page.setTag(holder);
 
         myHolder.mPageInAdapter = position;
@@ -339,16 +335,9 @@
         }
         content.init(provider, mMediaSize, mMinMargins);
 
-
         View pageSelector = page.findViewById(R.id.page_selector);
         pageSelector.setTag(myHolder);
-        if (pageCount > 1) {
-            pageSelector.setOnClickListener(mPageClickListener);
-            pageSelector.setVisibility(View.VISIBLE);
-        } else {
-            pageSelector.setOnClickListener(null);
-            pageSelector.setVisibility(View.GONE);
-        }
+        pageSelector.setOnClickListener(mPageClickListener);
 
         if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) {
             pageSelector.setSelected(true);
@@ -449,8 +438,9 @@
 
         final int verticalPadding;
         if (mPageContentHeight + mFooterHeight + mPreviewListPadding > availableHeight) {
-            verticalPadding = Math.max(mPreviewPageMargin,
-                    (availableHeight - totalContentHeight) / 2);
+            verticalPadding = Math.max(0,
+                    (availableHeight - mPageContentHeight - mFooterHeight) / 2
+                            - mPreviewPageMargin);
         } else {
             verticalPadding = Math.max(mPreviewListPadding,
                     (availableHeight - totalContentHeight) / 2);
@@ -791,6 +781,9 @@
                 page.animate().translationZ(mSelectedPageElevation)
                         .alpha(mSelectedPageAlpha);
             } else {
+                if (mConfirmedPagesInDocument.size() <= 1) {
+                    return;
+                }
                 mConfirmedPagesInDocument.remove(pageInDocument);
                 pageSelector.setSelected(false);
                 page.animate().translationZ(mUnselectedPageElevation)
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index fe17516..01c9746 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -982,21 +982,21 @@
 
         // Media size.
         mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
-                this, R.layout.spinner_dropdown_item, R.id.title);
+                this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
         mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
         mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
         mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener);
 
         // Color mode.
         mColorModeSpinnerAdapter = new ArrayAdapter<>(
-                this, R.layout.spinner_dropdown_item, R.id.title);
+                this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
         mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
         mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
         mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener);
 
         // Orientation
         mOrientationSpinnerAdapter = new ArrayAdapter<>(
-                this, R.layout.spinner_dropdown_item, R.id.title);
+                this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
         String[] orientationLabels = getResources().getStringArray(
                 R.array.orientation_labels);
         mOrientationSpinnerAdapter.add(new SpinnerItem<>(
@@ -1008,8 +1008,8 @@
         mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener);
 
         // Range options
-        ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
-                new ArrayAdapter<>(this, R.layout.spinner_dropdown_item, R.id.title);
+        ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>(
+                this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
         mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
         mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter);
         mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener);
@@ -1075,6 +1075,7 @@
                 mDestinationSpinner.setEnabled(false);
             }
             mCopiesEditText.setEnabled(false);
+            mCopiesEditText.setFocusable(false);
             mMediaSizeSpinner.setEnabled(false);
             mColorModeSpinner.setEnabled(false);
             mOrientationSpinner.setEnabled(false);
@@ -1089,6 +1090,7 @@
         // available, we disable all print options except the destination.
         if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
             mCopiesEditText.setEnabled(false);
+            mCopiesEditText.setFocusable(false);
             mMediaSizeSpinner.setEnabled(false);
             mColorModeSpinner.setEnabled(false);
             mOrientationSpinner.setEnabled(false);
@@ -1316,8 +1318,10 @@
         // Copies
         if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
             mCopiesEditText.setEnabled(true);
+            mCopiesEditText.setFocusableInTouchMode(true);
         } else {
             mCopiesEditText.setEnabled(false);
+            mCopiesEditText.setFocusable(false);
         }
         if (mCopiesEditText.getError() == null
                 && TextUtils.isEmpty(mCopiesEditText.getText())) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java b/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java
deleted file mode 100644
index d6bb7c8..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 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.printspooler.widget;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.EditText;
-
-/**
- * An instance of this class class is intended to be the first focusable
- * in a layout to which the system automatically gives focus. It performs
- * some voodoo to avoid the first tap on it to start an edit mode, rather
- * to bring up the IME, i.e. to get the behavior as if the view was not
- * focused.
- */
-public final class FirstFocusableEditText extends EditText {
-    private boolean mClickedBeforeFocus;
-    private CharSequence mError;
-
-    public FirstFocusableEditText(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public boolean performClick() {
-        super.performClick();
-        if (isFocused() && !mClickedBeforeFocus) {
-            clearFocus();
-            requestFocus();
-        }
-        mClickedBeforeFocus = true;
-        return true;
-    }
-
-    @Override
-    public CharSequence getError() {
-        return mError;
-    }
-
-    @Override
-    public void setError(CharSequence error, Drawable icon) {
-        setCompoundDrawables(null, null, icon, null);
-        mError = error;
-    }
-
-    protected void onFocusChanged(boolean gainFocus, int direction,
-            Rect previouslyFocusedRect) {
-        if (!gainFocus) {
-            mClickedBeforeFocus = false;
-        }
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-    }
-}
\ No newline at end of file
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index e428948..c84b06a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -152,6 +152,17 @@
 
         // Make sure we start in a closed options state.
         onDragProgress(1.0f);
+
+        // The framework gives focus to the frist focusable and we
+        // do not want that, hence we will take focus instead.
+        setFocusableInTouchMode(true);
+    }
+
+    @Override
+    public void focusableViewAvailable(View v) {
+        // The framework gives focus to the frist focusable and we
+        // do not want that, hence do not announce new focusables.
+        return;
     }
 
     @Override
@@ -309,6 +320,7 @@
             mSummaryContent.setLayerType(View.LAYER_TYPE_NONE, null);
             mDraggableContent.setLayerType(View.LAYER_TYPE_NONE, null);
             mMoreOptionsButton.setLayerType(View.LAYER_TYPE_NONE, null);
+            mMoreOptionsButton.setLayerType(View.LAYER_TYPE_NONE, null);
         }
 
         mDragProgress = progress;
@@ -320,7 +332,6 @@
         mMoreOptionsButton.setAlpha(inverseAlpha);
 
         mEmbeddedContentScrim.setBackgroundColor(computeScrimColor());
-
         if (progress == 0) {
             if (mOptionsStateChangeListener != null) {
                 mOptionsStateChangeListener.onOptionsOpened();
@@ -354,14 +365,15 @@
     }
 
     private void ensureImeClosedAndInputFocusCleared() {
-        View focus = findFocus();
-        if (focus != null) {
+        View focused = findFocus();
+
+        if (focused != null && focused.isFocused()) {
             InputMethodManager imm = (InputMethodManager) mContext.getSystemService(
                     Context.INPUT_METHOD_SERVICE);
-            if (imm.isActive(focus)) {
+            if (imm.isActive(focused)) {
                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
             }
-            focus.clearFocus();
+            focused.clearFocus();
         }
     }
 
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 299e50c..a3bed4f 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -187,6 +187,9 @@
     <!-- Default for Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1==on -->
     <integer name="def_lock_screen_show_notifications">1</integer>
 
+    <!-- Default for Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS -->
+    <bool name="def_lock_screen_allow_private_notifications">true</bool>
+
     <!-- Default for Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, 1==on -->
     <integer name="def_heads_up_enabled">1</integer>
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index edefb13..fd5e6fe 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -70,7 +70,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 108;
+    private static final int DATABASE_VERSION = 109;
 
     private Context mContext;
     private int mUserHandle;
@@ -1673,8 +1673,8 @@
             try {
                 stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)"
                         + " VALUES(?,?);");
-                loadBooleanSetting(stmt, Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                        R.bool.def_guest_user_enabled);
+                loadIntegerSetting(stmt, Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                        R.integer.def_lock_screen_show_notifications);
                 if (mUserHandle == UserHandle.USER_OWNER) {
                     final int oldShow = getIntValueFromTable(db,
                             TABLE_GLOBAL, Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, -1);
@@ -1733,6 +1733,22 @@
             upgradeVersion = 108;
         }
 
+        if (upgradeVersion < 109) {
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)"
+                        + " VALUES(?,?);");
+                loadBooleanSetting(stmt, Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+                        R.bool.def_lock_screen_allow_private_notifications);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 109;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -2301,6 +2317,9 @@
             loadIntegerSetting(stmt, Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                     R.integer.def_lock_screen_show_notifications);
 
+            loadBooleanSetting(stmt, Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+                    R.bool.def_lock_screen_allow_private_notifications);
+
         } finally {
             if (stmt != null) stmt.close();
         }
diff --git a/packages/SystemUI/res/drawable/ic_android.xml b/packages/SystemUI/res/drawable/ic_android.xml
new file mode 100644
index 0000000..19ee9a7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_android.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2014 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M6.000000,18.000000c0.000000,0.600000 0.400000,1.000000 1.000000,1.000000l1.000000,0.000000l0.000000,3.500000C8.000000,23.299999 8.700000,24.000000 9.500000,24.000000c0.800000,0.000000 1.500000,-0.700000 1.500000,-1.500000L11.000000,19.000000l2.000000,0.000000l0.000000,3.500000c0.000000,0.800000 0.700000,1.500000 1.500000,1.500000c0.800000,0.000000 1.500000,-0.700000 1.500000,-1.500000L16.000000,19.000000l1.000000,0.000000c0.600000,0.000000 1.000000,-0.400000 1.000000,-1.000000L18.000000,8.000000L6.000000,8.000000L6.000000,18.000000zM3.500000,8.000000C2.700000,8.000000 2.000000,8.700000 2.000000,9.500000l0.000000,7.000000C2.000000,17.299999 2.700000,18.000000 3.500000,18.000000C4.300000,18.000000 5.000000,17.299999 5.000000,16.500000l0.000000,-7.000000C5.000000,8.700000 4.300000,8.000000 3.500000,8.000000zM20.500000,8.000000C19.700001,8.000000 19.000000,8.700000 19.000000,9.500000l0.000000,7.000000c0.000000,0.800000 0.700000,1.500000 1.500000,1.500000c0.800000,0.000000 1.500000,-0.700000 1.500000,-1.500000l0.000000,-7.000000C22.000000,8.700000 21.299999,8.000000 20.500000,8.000000zM15.500000,2.200000l1.300000,-1.300000c0.200000,-0.200000 0.200000,-0.500000 0.000000,-0.700000c-0.200000,-0.200000 -0.500000,-0.200000 -0.700000,0.000000l-1.500000,1.500000C13.900000,1.200000 13.000000,1.000000 12.000000,1.000000c-1.000000,0.000000 -1.900000,0.200000 -2.700000,0.600000L7.900000,0.100000C7.700000,0.000000 7.300000,0.000000 7.100000,0.100000C7.000000,0.300000 7.000000,0.700000 7.100000,0.900000l1.300000,1.300000C7.000000,3.300000 6.000000,5.000000 6.000000,7.000000l12.000000,0.000000C18.000000,5.000000 17.000000,3.200000 15.500000,2.200000zM10.000000,5.000000L9.000000,5.000000L9.000000,4.000000l1.000000,0.000000L10.000000,5.000000zM15.000000,5.000000l-1.000000,0.000000L14.000000,4.000000l1.000000,0.000000L15.000000,5.000000z"
+        android:fillColor="#ffffffff"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_close.xml b/packages/SystemUI/res/drawable/ic_close.xml
new file mode 100644
index 0000000..7d93d45
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_close.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2014 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+        android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index ef0c9bb..ca07c87 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -67,6 +67,6 @@
         android:src="@drawable/ic_lock_24dp"
         android:scaleType="center"
         android:tint="#ffffffff"
-        android:contentDescription="@string/accessibility_unlock_button_not_secured" />
+        android:contentDescription="@string/accessibility_unlock_button" />
 
 </com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index eff3758..351177b 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -38,8 +38,8 @@
             android:id="@+id/more_text"
             android:layout_width="32dp"
             android:layout_height="32dp"
-            android:layout_marginStart="20dp"
-            android:layout_marginEnd="16dp"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="12dp"
             android:layout_gravity="center_vertical"
             android:background="@drawable/keyguard_overflow_number_background"
             android:gravity="center"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b000a48..b488c56 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -213,6 +213,8 @@
     <string name="accessibility_camera_button">Camera</string>
     <!-- Content description of the phone button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_phone_button">Phone</string>
+    <!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_unlock_button">Unlock</string>
     <!-- Click action label for accessibility for the unlock button. [CHAR LIMIT=NONE] -->
     <string name="unlock_label">unlock</string>
     <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
@@ -220,17 +222,6 @@
     <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
     <string name="camera_label">open camera</string>
 
-    <!-- Content description of the lock icon when device is secured (lock closed) and trust not managed (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button_secured">Device secured.</string>
-    <!-- Content description of the lock icon when device is not secured (lock open) and trust not managed (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button_not_secured">Device not secured.</string>
-    <!-- Content description of the lock icon when device is secured (lock closed) and trust managed (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button_secured_trust_managed">Device secured, trust agent active.</string>
-    <!-- Content description of the lock icon when device is not secured (lock open) and trust managed (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button_not_secured_trust_managed">Device not secured, trust agent active.</string>
-    <!-- Content description of the lock icon when face unlock is running (face icon) and trust managed (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button_face_unlock_running">Face detection running, trust agent active.</string>
-
     <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_ime_switch_button">Switch input method button.</string>
     <!-- Content description of the compatibility zoom button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -323,6 +314,15 @@
     <!-- Content description of an item with full signal for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_signal_full">Signal full.</string>
 
+    <!-- Content description of an item that is turned on for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_desc_on">On.</string>
+    <!-- Content description of an item that is turned off for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_desc_off">Off.</string>
+    <!-- Content description of an item that is connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_desc_connected">Connected.</string>
+    <!-- Content description of an item that is connecting for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_desc_connecting">Connecting.</string>
+
     <!-- Content description of the data connection type GPRS for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_data_connection_gprs">GPRS</string>
 
@@ -894,6 +894,18 @@
     <!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] -->
     <string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string>
 
+    <!-- Title of notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=40] -->
+    <string name="hidden_notifications_title">Get notifications faster</string>
+
+    <!-- Body of notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=60] -->
+    <string name="hidden_notifications_text">See them before you unlock</string>
+
+    <!-- Cancel action for notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=10] -->
+    <string name="hidden_notifications_cancel">No thanks</string>
+
+    <!-- continue action for notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=10] -->
+    <string name="hidden_notifications_setup">Set up</string>
+
     <!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
     <string name="muted_by">Muted by <xliff:g id="third_party">%1$s</xliff:g></string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 1ac3bc3..0c6e7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -67,6 +67,7 @@
     ArrayList<TaskStack> mStacks;
     View mSearchBar;
     RecentsViewCallbacks mCb;
+    boolean mAlreadyLaunchingTask;
 
     public RecentsView(Context context) {
         super(context);
@@ -120,6 +121,9 @@
             }
             addView(stackView);
         }
+
+        // Reset the launched state
+        mAlreadyLaunchingTask = false;
     }
 
     /** Removes all the task stack views from this recents view. */
@@ -381,6 +385,11 @@
         if (mCb != null) {
             mCb.onTaskViewClicked();
         }
+        // Skip if we are already launching tasks
+        if (mAlreadyLaunchingTask) {
+            return;
+        }
+        mAlreadyLaunchingTask = true;
 
         // Upfront the processing of the thumbnail
         TaskViewTransform transform = new TaskViewTransform();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index da99118..f04c8c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.TaskStackBuilder;
 import android.app.admin.DevicePolicyManager;
@@ -35,7 +36,11 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -78,6 +83,7 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.statusbar.StatusBarIconList;
 import com.android.internal.util.NotificationColorUtil;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SearchPanelView;
@@ -126,6 +132,12 @@
     public static final int EXPANDED_LEAVE_ALONE = -10000;
     public static final int EXPANDED_FULL_OPEN = -10001;
 
+    private static final int HIDDEN_NOTIFICATION_ID = 10000;
+    private static final String BANNER_ACTION_CANCEL =
+            "com.android.systemui.statusbar.banner_action_cancel";
+    private static final String BANNER_ACTION_SETUP =
+            "com.android.systemui.statusbar.banner_action_setup";
+
     protected CommandQueue mCommandQueue;
     protected IStatusBarService mBarService;
     protected H mHandler = createHandler();
@@ -308,6 +320,20 @@
                 mUsersAllowingPrivateNotifications.clear();
                 updateLockscreenNotificationSetting();
                 updateNotifications();
+            } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
+                NotificationManager noMan = (NotificationManager)
+                        mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                noMan.cancel(HIDDEN_NOTIFICATION_ID);
+
+                Settings.Secure.putInt(mContext.getContentResolver(),
+                        Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+                if (BANNER_ACTION_SETUP.equals(action)) {
+                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+                    mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
+                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                    );
+                }
             }
         }
     };
@@ -490,12 +516,61 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(BANNER_ACTION_CANCEL);
+        filter.addAction(BANNER_ACTION_SETUP);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
         updateCurrentProfilesCache();
     }
 
+    protected void notifyUserAboutHiddenNotifications() {
+        if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
+            Log.d(TAG, "user hasn't seen notification about hidden notifications");
+            final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
+            if (!lockPatternUtils.isSecure()) {
+                Log.d(TAG, "insecure lockscreen, skipping notification");
+                Settings.Secure.putInt(mContext.getContentResolver(),
+                        Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+                return;
+            }
+            Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+            // disable lockscreen notifications until user acts on the banner.
+            Settings.Secure.putInt(mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+
+            final String packageName = mContext.getPackageName();
+            PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0,
+                    new Intent(BANNER_ACTION_CANCEL).setPackage(packageName),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0,
+                    new Intent(BANNER_ACTION_SETUP).setPackage(packageName),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+
+            final Resources res = mContext.getResources();
+            final int colorRes = com.android.internal.R.color.system_notification_accent_color;
+            Notification.Builder note = new Notification.Builder(mContext)
+                    .setSmallIcon(R.drawable.ic_android)
+                    .setContentTitle(mContext.getString(R.string.hidden_notifications_title))
+                    .setContentText(mContext.getString(R.string.hidden_notifications_text))
+                    .setPriority(Notification.PRIORITY_HIGH)
+                    .setOngoing(true)
+                    .setColor(res.getColor(colorRes))
+                    .setContentIntent(setupIntent)
+                    .addAction(R.drawable.ic_close,
+                            mContext.getString(R.string.hidden_notifications_cancel),
+                            cancelIntent)
+                    .addAction(R.drawable.ic_settings,
+                            mContext.getString(R.string.hidden_notifications_setup),
+                            setupIntent);
+
+            NotificationManager noMan =
+                    (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+            noMan.notify(HIDDEN_NOTIFICATION_ID, note.build());
+        }
+    }
+
     public void userSwitched(int newUserId) {
         // should be overridden
     }
@@ -582,9 +657,11 @@
 
     protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
             NotificationData.Entry entry) {
+        PackageManager pmUser = getPackageManagerForUser(
+                entry.notification.getUser().getIdentifier());
         int version = 0;
         try {
-            ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
+            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
             version = info.targetSdkVersion;
         } catch (NameNotFoundException ex) {
             Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 62552b2..f9da30f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -380,21 +380,10 @@
         mLockIcon.setImageResource(iconRes);
         boolean trustManaged = mUnlockMethodCache.isTrustManaged();
         mTrustDrawable.setTrustManaged(trustManaged);
-
         updateLockIconClickability();
-        updateLockIconContentDescription(mUnlockMethodCache.isFaceUnlockRunning(),
-                mUnlockMethodCache.isMethodInsecure(), trustManaged);
     }
 
-    private void updateLockIconContentDescription(boolean faceUnlockRunning, boolean insecure,
-            boolean trustManaged) {
-        mLockIcon.setContentDescription(getResources().getString(
-                faceUnlockRunning ? R.string.accessibility_unlock_button_face_unlock_running
-                : insecure && !trustManaged ? R.string.accessibility_unlock_button_not_secured
-                : insecure ? R.string.accessibility_unlock_button_not_secured_trust_managed
-                : !trustManaged ? R.string.accessibility_unlock_button_secured
-                : R.string.accessibility_unlock_button_secured_trust_managed));
-    }
+
 
     public KeyguardAffordanceView getPhoneView() {
         return mPhoneImageView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9fd3d9c..1a0d2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -582,6 +582,8 @@
         putComponent(PhoneStatusBar.class, this);
 
         setControllerUsers();
+
+        notifyUserAboutHiddenNotifications();
     }
 
     // ================================================================================
@@ -3612,7 +3614,8 @@
     }
 
     public boolean onSpacePressed() {
-        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+        if (mScreenOn != null && mScreenOn
+                && (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
             animateCollapsePanels(0 /* flags */, true /* force */);
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index f03c5eb..12c887e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -26,7 +26,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -46,7 +45,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Vibrator;
-import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.KeyEvent;
@@ -58,6 +56,7 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageView;
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
@@ -97,6 +96,7 @@
     private static final int TIMEOUT_DELAY_SHORT = 1500;
     private static final int TIMEOUT_DELAY_COLLAPSED = 4500;
     private static final int TIMEOUT_DELAY_SAFETY_WARNING = 5000;
+    private static final int TIMEOUT_DELAY_SAFETY_WARNING_TALKBACK = 25000;
     private static final int TIMEOUT_DELAY_EXPANDED = 10000;
 
     private static final int MSG_VOLUME_CHANGED = 0;
@@ -161,6 +161,7 @@
     private int mActiveStreamType = -1;
     /** All the slider controls mapped by stream type */
     private SparseArray<StreamControl> mStreamControls;
+    private final AccessibilityManager mAccessibilityManager;
 
     private enum StreamResources {
         BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
@@ -332,6 +333,8 @@
         mContext = context;
         mZenController = zenController;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mAccessibilityManager = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
 
         // For now, only show master volume if master volume is supported
         final Resources res = context.getResources();
@@ -791,7 +794,8 @@
     }
 
     private void updateTimeoutDelay() {
-        mTimeoutDelay = sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING
+        mTimeoutDelay = sSafetyWarning != null ? mAccessibilityManager.isEnabled() ?
+                TIMEOUT_DELAY_SAFETY_WARNING_TALKBACK : TIMEOUT_DELAY_SAFETY_WARNING
                 : mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT
                 : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED
                 : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED
@@ -1214,6 +1218,7 @@
             }
             updateStates();
         }
+        updateTimeoutDelay();
         resetTimeout();
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java b/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java
index 71b0d53..6f79f58 100644
--- a/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java
+++ b/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java
@@ -83,6 +83,7 @@
 
 
     private final Context mContext;
+    private final Runnable mOnAccessibilityEnabledCallback;
     private final UserManager mUserManager;
     private final TextToSpeech mTts;
     private final Ringtone mTone;
@@ -97,8 +98,9 @@
     private float mSecondPointerDownX;
     private float mSecondPointerDownY;
 
-    public EnableAccessibilityController(Context context) {
+    public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
         mContext = context;
+        mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
             @Override
@@ -275,5 +277,7 @@
                 /* ignore */
             }
         }
+
+        mOnAccessibilityEnabledCallback.run();
     }
 }
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index ae94654..41695c1 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -1073,7 +1073,13 @@
             // is dismissed on the first down while the global gesture is a long press
             // with two fingers anywhere on the screen.
             if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
-                mEnableAccessibilityController = new EnableAccessibilityController(mContext);
+                mEnableAccessibilityController = new EnableAccessibilityController(mContext,
+                        new Runnable() {
+                    @Override
+                    public void run() {
+                        dismiss();
+                    }
+                });
                 super.setCanceledOnTouchOutside(false);
             } else {
                 mEnableAccessibilityController = null;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index a13da609..93591a9 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -2754,11 +2754,13 @@
                 mStatusColorView = updateColorViewInt(mStatusColorView,
                         SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
                         mStatusBarColor, mLastTopInset, Gravity.TOP,
-                        STATUS_BAR_BACKGROUND_TRANSITION_NAME);
+                        STATUS_BAR_BACKGROUND_TRANSITION_NAME,
+                        com.android.internal.R.id.statusBarBackground);
                 mNavigationColorView = updateColorViewInt(mNavigationColorView,
                         SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
                         mNavigationBarColor, mLastBottomInset, Gravity.BOTTOM,
-                        NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME);
+                        NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
+                        com.android.internal.R.id.navigationBarBackground);
             }
             if (insets != null) {
                 insets = insets.consumeStableInsets();
@@ -2767,7 +2769,7 @@
         }
 
         private View updateColorViewInt(View view, int systemUiHideFlag, int translucentFlag,
-                int color, int height, int verticalGravity, String transitionName) {
+                int color, int height, int verticalGravity, String transitionName, int id) {
             boolean show = height > 0 && (mLastSystemUiVisibility & systemUiHideFlag) == 0
                     && (getAttributes().flags & translucentFlag) == 0
                     && (color & Color.BLACK) != 0
@@ -2778,6 +2780,7 @@
                     view = new View(mContext);
                     view.setBackgroundColor(color);
                     view.setTransitionName(transitionName);
+                    view.setId(id);
                     addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, height,
                             Gravity.START | verticalGravity));
                 }
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index ac0ca0a..af5c13d 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -708,6 +708,7 @@
                      // Send an event to the end of the drag gesture.
                      sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                  }
+                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
             case MotionEvent.ACTION_UP: {
                 mAms.onTouchInteractionEnd();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1367761..6310764 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8948,6 +8948,14 @@
         return false;
     }
 
+    private void checkTime(long startTime, String where) {
+        long now = SystemClock.elapsedRealtime();
+        if ((now-startTime) > 1000) {
+            // If we are taking more than a second, log about it.
+            Slog.w(TAG, "Slow operation: " + (now-startTime) + "ms so far, now at " + where);
+        }
+    }
+
     private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
             String name, IBinder token, boolean stable, int userId) {
         ContentProviderRecord cpr;
@@ -8955,6 +8963,8 @@
         ProviderInfo cpi = null;
 
         synchronized(this) {
+            long startTime = SystemClock.elapsedRealtime();
+
             ProcessRecord r = null;
             if (caller != null) {
                 r = getRecordForAppLocked(caller);
@@ -8968,6 +8978,8 @@
 
             boolean checkCrossUser = true;
 
+            checkTime(startTime, "getContentProviderImpl: getProviderByName");
+
             // First check if this content provider has been published...
             cpr = mProviderMap.getProviderByName(name, userId);
             // If that didn't work, check if it exists for user 0 and then
@@ -8992,10 +9004,12 @@
             if (providerRunning) {
                 cpi = cpr.info;
                 String msg;
+                checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
                 if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
                         != null) {
                     throw new SecurityException(msg);
                 }
+                checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
 
                 if (r != null && cpr.canRunHere(r)) {
                     // This provider has been published or is in the process
@@ -9011,6 +9025,8 @@
 
                 final long origId = Binder.clearCallingIdentity();
 
+                checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");
+
                 // In this case the provider instance already exists, so we can
                 // return it right away.
                 conn = incProviderCountLocked(r, cpr, token, stable);
@@ -9020,7 +9036,9 @@
                         // make sure to count it as being accessed and thus
                         // back up on the LRU list.  This is good because
                         // content providers are often expensive to start.
+                        checkTime(startTime, "getContentProviderImpl: before updateLruProcess");
                         updateLruProcessLocked(cpr.proc, false, null);
+                        checkTime(startTime, "getContentProviderImpl: after updateLruProcess");
                     }
                 }
 
@@ -9033,7 +9051,9 @@
                             Process.killProcess(cpr.proc.pid);
                         }
                     }
+                    checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                     boolean success = updateOomAdjLocked(cpr.proc);
+                    checkTime(startTime, "getContentProviderImpl: after updateOomAdj");
                     if (DEBUG_PROVIDER) Slog.i(TAG, "Adjust success: " + success);
                     // NOTE: there is still a race here where a signal could be
                     // pending on the process even though we managed to update its
@@ -9048,7 +9068,9 @@
                                 "Existing provider " + cpr.name.flattenToShortString()
                                 + " is crashing; detaching " + r);
                         boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
+                        checkTime(startTime, "getContentProviderImpl: before appDied");
                         appDiedLocked(cpr.proc);
+                        checkTime(startTime, "getContentProviderImpl: after appDied");
                         if (!lastRef) {
                             // This wasn't the last ref our process had on
                             // the provider...  we have now been killed, bail.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1d2f7a9..6545134 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2072,7 +2072,7 @@
             }
             targetStack = inTask.stack;
             targetStack.moveTaskToFrontLocked(inTask, r, options);
-            mWindowManager.moveTaskToTop(targetStack.topTask().taskId);
+            mWindowManager.moveTaskToTop(inTask.taskId);
 
             // Check whether we should actually launch the new activity in to the task,
             // or just reuse the current activity on top.
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index ff22764..a37249d 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -166,8 +166,16 @@
         }
         if (full) {
             if (hasExternalProcessHandles()) {
-                pw.print(prefix); pw.print("externals=");
-                        pw.println(externalProcessTokenToHandle.size());
+                pw.print(prefix); pw.print("externals:");
+                if (externalProcessTokenToHandle != null) {
+                    pw.print(" w/token=");
+                    pw.print(externalProcessTokenToHandle.size());
+                }
+                if (externalProcessNoHandleCount > 0) {
+                    pw.print(" notoken=");
+                    pw.print(externalProcessNoHandleCount);
+                }
+                pw.println();
             }
         } else {
             if (connections.size() > 0 || externalProcessNoHandleCount > 0) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 77c324f..1287dce 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -21,6 +21,7 @@
 import java.nio.ByteBuffer;
 
 import android.app.ActivityManager;
+import android.os.SystemClock;
 import com.android.internal.util.MemInfoReader;
 import com.android.server.wm.WindowManagerService;
 
@@ -528,12 +529,18 @@
         if (amt == UNKNOWN_ADJ)
             return;
 
+        long start = SystemClock.elapsedRealtime();
         ByteBuffer buf = ByteBuffer.allocate(4 * 4);
         buf.putInt(LMK_PROCPRIO);
         buf.putInt(pid);
         buf.putInt(uid);
         buf.putInt(amt);
         writeLmkd(buf);
+        long now = SystemClock.elapsedRealtime();
+        if ((now-start) > 250) {
+            Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
+                    + " = " + amt);
+        }
     }
 
     /*
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 38077eb..8c342dd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1009,7 +1009,7 @@
                 y[i] = normalizeAbsoluteBrightness(brightness[i]);
             }
 
-            Spline spline = Spline.createMonotoneCubicSpline(x, y);
+            Spline spline = Spline.createSpline(x, y);
             if (DEBUG) {
                 Slog.d(TAG, "Auto-brightness spline: " + spline);
                 for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 827b3ed..bb22b4d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -23,6 +23,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Predicate;
 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -30,6 +31,8 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -446,7 +449,7 @@
                         allocated.add(address);
                     }
                 }
-                mIoThreadLogger.debug("DevicePollingResult:" + allocated);
+                mIoThreadLogger.debug("[P]:Allocated Address=" + allocated);
                 if (callback != null) {
                     runOnServiceThread(new Runnable() {
                         @Override
@@ -548,7 +551,7 @@
         runOnIoThread(new Runnable() {
             @Override
             public void run() {
-                mIoThreadLogger.debug("SendCommand:" + cecMessage);
+                mIoThreadLogger.debug("[S]:" + cecMessage);
                 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
                 int i = 0;
                 int errorCode = Constants.SEND_RESULT_SUCCESS;
@@ -583,7 +586,7 @@
     private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
         assertRunOnServiceThread();
         HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
-        mServiceThreadLogger.debug("ReceiveCommand:" + command);
+        mServiceThreadLogger.debug("[R]:" + command);
         onReceiveCommand(command);
     }
 
@@ -598,6 +601,15 @@
         mService.onHotplug(port, connected);
     }
 
+    void dump(final IndentingPrintWriter pw) {
+        for (int i = 0; i < mLocalDevices.size(); ++i) {
+            pw.println("HdmiCecLocalDevice #" + i + ":");
+            pw.increaseIndent();
+            mLocalDevices.valueAt(i).dump(pw);
+            pw.decreaseIndent();
+        }
+    }
+
     private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
     private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
             int dstAddress, byte[] body);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 26d2cde..85f5be2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -43,6 +43,9 @@
  */
 abstract class HdmiCecFeatureAction {
     private static final String TAG = "HdmiCecFeatureAction";
+    // As all actions run in the same thread (service thread), it's fine to have single logger.
+    // TODO: create global logger for each threads and use them.
+    protected static final HdmiLogger DLOGGER = new HdmiLogger(TAG);
 
     // Timer handler message used for timeout event
     protected static final int MSG_TIMEOUT = 100;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index a12e4fc..38addba 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -28,6 +28,7 @@
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
 import java.util.ArrayList;
@@ -97,6 +98,17 @@
         public int hashCode() {
             return logicalAddress * 29 + physicalAddress;
         }
+        @Override
+        public String toString() {
+            StringBuffer s = new StringBuffer();
+            String logicalAddressString = (logicalAddress == Constants.ADDR_INVALID)
+                    ? "invalid" : String.format("0x%02x", logicalAddress);
+            s.append("logical_address: ").append(logicalAddressString);
+            String physicalAddressString = (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS)
+                    ? "invalid" : String.format("0x%04x", physicalAddress);
+            s.append(", physical_address: ").append(physicalAddressString);
+            return s.toString();
+        }
     }
     // Logical address of the active source.
     @GuardedBy("mLock")
@@ -793,4 +805,16 @@
     protected void sendKeyEvent(int keyCode, boolean isPressed) {
         Slog.w(TAG, "sendKeyEvent not implemented");
     }
+
+    /**
+     * Dump internal status of HdmiCecLocalDevice object.
+     */
+    protected void dump(final IndentingPrintWriter pw) {
+        pw.println("mDeviceType: " + mDeviceType);
+        pw.println("mAddress: " + mAddress);
+        pw.println("mPreferredAddress: " + mPreferredAddress);
+        pw.println("mDeviceInfo: " + mDeviceInfo);
+        pw.println("mActiveSource: " + mActiveSource);
+        pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 5a2fa9c..6603a71 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -23,6 +23,7 @@
 import android.os.SystemProperties;
 import android.util.Slog;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
 /**
@@ -219,4 +220,10 @@
         mIsActiveSource = false;
         checkIfPendingActionsCleared();
     }
+
+    @Override
+    protected void dump(final IndentingPrintWriter pw) {
+        super.dump(pw);
+        pw.println("mIsActiveSource: " + mIsActiveSource);
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index cd56cfc..1ab8069 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -45,6 +45,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
@@ -1610,4 +1611,16 @@
 
         invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
     }
+
+    @Override
+    protected void dump(final IndentingPrintWriter pw) {
+        super.dump(pw);
+        pw.println("mArcEstablished: " + mArcEstablished);
+        pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
+        pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
+        pw.println("mSystemAudioMute: " + mSystemAudioMute);
+        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
+        pw.println("mAutoWakeup: " + mAutoWakeup);
+        pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e7b920a..d13e1de 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -67,6 +67,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.SystemService;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
@@ -75,6 +76,8 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -1395,6 +1398,28 @@
             enforceAccessPermission();
             HdmiControlService.this.addHdmiMhlScratchpadCommandListener(listener);
         }
+
+        @Override
+        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+
+            pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
+            pw.println("mProhibitMode: " + mProhibitMode);
+            if (mCecController != null) {
+                pw.println("mCecController: ");
+                pw.increaseIndent();
+                mCecController.dump(pw);
+                pw.decreaseIndent();
+            }
+            pw.println("mPortInfo: ");
+            pw.increaseIndent();
+            for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
+                pw.println("- " + hdmiPortInfo);
+            }
+            pw.decreaseIndent();
+            pw.println("mPowerStatus: " + mPowerStatus);
+        }
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java
index ee9379d..c7add75 100644
--- a/services/core/java/com/android/server/hdmi/HdmiLogger.java
+++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java
@@ -42,7 +42,7 @@
     private final String mTag;
 
     HdmiLogger(String tag) {
-        mTag = tag;
+        mTag = "HDMI:" + tag;
     }
 
     void warning(String logMessage) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index ac2c7b9..d15ffb0 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -73,9 +73,9 @@
 
     // Seq #27
     protected void sendSystemAudioModeRequest() {
-        mState = STATE_CHECK_ROUTING_IN_PRGRESS;
         List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class);
         if (!routingActions.isEmpty()) {
+            mState = STATE_CHECK_ROUTING_IN_PRGRESS;
             // Should have only one Routing Control Action
             RoutingControlAction routingAction = routingActions.get(0);
             routingAction.addOnFinishedCallback(this, new Runnable() {
@@ -97,20 +97,21 @@
         sendCommand(command, new HdmiControlService.SendMessageCallback() {
             @Override
             public void onSendCompleted(int error) {
-                if (error == Constants.SEND_RESULT_SUCCESS) {
-                    mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE;
-                    addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS);
-                } else {
+                if (error != Constants.SEND_RESULT_SUCCESS) {
+                    DLOGGER.debug("Failed to send <System Audio Mode Request>:" + error);
                     setSystemAudioMode(false);
                     finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
                 }
             }
         });
+        mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE;
+        addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS);
     }
 
     private void handleSendSystemAudioModeRequestTimeout() {
         if (!mTargetAudioStatus  // Don't retry for Off case.
                 || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) {
+            DLOGGER.debug("[T]:wait for <Set System Audio Mode>.");
             setSystemAudioMode(false);
             finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
             return;
@@ -129,6 +130,7 @@
                 if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT
                         && (cmd.getParams()[0] & 0xFF)
                                 == Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST) {
+                    DLOGGER.debug("Failed to start system audio mode request.");
                     setSystemAudioMode(false);
                     finishWithCallback(HdmiControlManager.RESULT_EXCEPTION);
                     return true;
@@ -143,6 +145,7 @@
                     startAudioStatusAction();
                     return true;
                 } else {
+                    DLOGGER.debug("Unexpected system audio mode request:" + receivedStatus);
                     // Unexpected response, consider the request is newly initiated by AVR.
                     // To return 'false' will initiate new SystemAudioActionFromAvr by the control
                     // service.
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 379ec94..c3bc306 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -819,6 +819,7 @@
     };
 
     void dumpInternal(PrintWriter pw) {
+        final long now = SystemClock.elapsedRealtime();
         synchronized (mJobs) {
             pw.print("Started users: ");
             for (int i=0; i<mStartedUsers.size(); i++) {
@@ -833,15 +834,14 @@
                     job.dump(pw, "  ");
                 }
             } else {
-                pw.println();
-                pw.println("No jobs scheduled.");
+                pw.println("  None.");
             }
             for (int i=0; i<mControllers.size(); i++) {
                 pw.println();
                 mControllers.get(i).dumpControllerState(pw);
             }
             pw.println();
-            pw.println("Pending");
+            pw.println("Pending:");
             for (int i=0; i<mPendingJobs.size(); i++) {
                 pw.println(mPendingJobs.get(i).hashCode());
             }
@@ -852,10 +852,14 @@
                 if (jsc.isAvailable()) {
                     continue;
                 } else {
-                    pw.println(jsc.getRunningJob().hashCode() + " for: " +
-                            (SystemClock.elapsedRealtime()
-                                    - jsc.getExecutionStartTimeElapsed())/1000 + "s " +
-                            "timeout: " + jsc.getTimeoutElapsed());
+                    final long timeout = jsc.getTimeoutElapsed();
+                    pw.print("Running for: ");
+                    pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
+                    pw.print("s timeout=");
+                    pw.print(timeout);
+                    pw.print(" fromnow=");
+                    pw.println(timeout-now);
+                    jsc.getRunningJob().dump(pw, "  ");
                 }
             }
             pw.println();
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 6f5d3c2..f562721 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -251,6 +251,7 @@
 
     // Dumpsys infrastructure
     public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
         pw.println(this.toString());
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fc1b746..d0f4054 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.notification;
 
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService.TRIM_FULL;
+import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -50,6 +52,7 @@
 import android.media.IRingtonePlayer;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -1290,24 +1293,23 @@
          */
         @Override
         public ParceledListSlice<StatusBarNotification> getActiveNotificationsFromListener(
-                INotificationListener token, String[] keys) {
+                INotificationListener token, String[] keys, int trim) {
             synchronized (mNotificationList) {
                 final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                final ArrayList<StatusBarNotification> list
-                        = new ArrayList<StatusBarNotification>();
                 final boolean getKeys = keys != null;
                 final int N = getKeys ? keys.length : mNotificationList.size();
-                list.ensureCapacity(N);
+                final ArrayList<StatusBarNotification> list
+                        = new ArrayList<StatusBarNotification>(N);
                 for (int i=0; i<N; i++) {
                     final NotificationRecord r = getKeys
                             ? mNotificationsByKey.get(keys[i])
                             : mNotificationList.get(i);
-                    if (r != null) {
-                        StatusBarNotification sbn = r.sbn;
-                        if (isVisibleToListener(sbn, info)) {
-                            list.add(sbn);
-                        }
-                    }
+                    if (r == null) continue;
+                    StatusBarNotification sbn = r.sbn;
+                    if (!isVisibleToListener(sbn, info)) continue;
+                    StatusBarNotification sbnToSend =
+                            (trim == TRIM_FULL) ? sbn : sbn.cloneLight();
+                    list.add(sbnToSend);
                 }
                 return new ParceledListSlice<StatusBarNotification>(list);
             }
@@ -1364,6 +1366,16 @@
         }
 
         @Override
+        public void setOnNotificationPostedTrimFromListener(INotificationListener token, int trim)
+                throws RemoteException {
+            synchronized (mNotificationList) {
+                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                if (info == null) return;
+                mListeners.setOnNotificationPostedTrimLocked(info, trim);
+            }
+        }
+
+        @Override
         public ZenModeConfig getZenModeConfig() {
             enforceSystemOrSystemUI("INotificationManager.getZenModeConfig");
             return mZenModeHelper.getConfig();
@@ -1427,7 +1439,7 @@
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                     != PackageManager.PERMISSION_GRANTED) {
-                pw.println("Permission Denial: can't dump NotificationManager from from pid="
+                pw.println("Permission Denial: can't dump NotificationManager from pid="
                         + Binder.getCallingPid()
                         + ", uid=" + Binder.getCallingUid());
                 return;
@@ -1441,6 +1453,13 @@
             enforceSystemOrSystemUI("INotificationManager.getEffectsSuppressor");
             return mEffectsSuppressor;
         }
+
+        @Override
+        public boolean matchesCallFilter(Bundle extras) {
+            enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
+            return mZenModeHelper.matchesCallFilter(extras,
+                    mRankingHelper.findExtractor(ValidateNotificationPeople.class));
+        }
     };
 
     private String[] getActiveNotificationKeys(INotificationListener token) {
@@ -2611,6 +2630,8 @@
 
     public class NotificationListeners extends ManagedServices {
 
+        private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
+
         public NotificationListeners() {
             super(getContext(), mHandler, mNotificationList, mUserProfiles);
         }
@@ -2651,6 +2672,20 @@
             if (mListenersDisablingEffects.remove(removed)) {
                 updateListenerHintsLocked();
             }
+            mLightTrimListeners.remove(removed);
+        }
+
+        public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
+            if (trim == TRIM_LIGHT) {
+                mLightTrimListeners.add(info);
+            } else {
+                mLightTrimListeners.remove(info);
+            }
+        }
+
+        public int getOnNotificationPostedTrim(ManagedServiceInfo info) {
+            return mLightTrimListeners.contains(info) ? TRIM_LIGHT : TRIM_FULL;
+
         }
 
         /**
@@ -2661,8 +2696,10 @@
          * but isn't anymore.
          */
         public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
-            // make a copy in case changes are made to the underlying Notification object
-            final StatusBarNotification sbnClone = sbn.clone();
+            // Lazily initialized snapshots of the notification.
+            StatusBarNotification sbnClone = null;
+            StatusBarNotification sbnCloneLight = null;
+
             for (final ManagedServiceInfo info : mServices) {
                 boolean sbnVisible = isVisibleToListener(sbn, info);
                 boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
@@ -2684,10 +2721,20 @@
                     continue;
                 }
 
+                final int trim = mListeners.getOnNotificationPostedTrim(info);
+
+                if (trim == TRIM_LIGHT && sbnCloneLight == null) {
+                    sbnCloneLight = sbn.cloneLight();
+                } else if (trim == TRIM_FULL && sbnClone == null) {
+                    sbnClone = sbn.clone();
+                }
+                final StatusBarNotification sbnToPost =
+                        (trim == TRIM_FULL) ? sbnClone : sbnCloneLight;
+
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        notifyPosted(info, sbnClone, update);
+                        notifyPosted(info, sbnToPost, update);
                     }
                 });
             }
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 01188af..435177b 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -87,6 +87,17 @@
         mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>();
     }
 
+    public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
+        final int N = mSignalExtractors.length;
+        for (int i = 0; i < N; i++) {
+            final NotificationSignalExtractor extractor = mSignalExtractors[i];
+            if (extractorClass.equals(extractor.getClass())) {
+                return (T) extractor;
+            }
+        }
+        return null;
+    }
+
     public void extractSignals(NotificationRecord r) {
         final int N = mSignalExtractors.length;
         for (int i = 0; i < N; i++) {
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index bdc364c..aa47858 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -71,8 +71,17 @@
     private LruCache<String, LookupResult> mPeopleCache;
 
     private RankingReconsideration validatePeople(final NotificationRecord record) {
+        final String key = record.getKey();
+        final Bundle extras = record.getNotification().extras;
+        final float[] affinityOut = new float[1];
+        final RankingReconsideration rr = validatePeople(key, extras, affinityOut);
+        record.setContactAffinity(affinityOut[0]);
+        return rr;
+    }
+
+    private PeopleRankingReconsideration validatePeople(String key, Bundle extras,
+            float[] affinityOut) {
         float affinity = NONE;
-        Bundle extras = record.getNotification().extras;
         if (extras == null) {
             return null;
         }
@@ -82,7 +91,7 @@
             return null;
         }
 
-        if (INFO) Slog.i(TAG, "Validating: " + record.sbn.getKey());
+        if (INFO) Slog.i(TAG, "Validating: " + key);
         final LinkedList<String> pendingLookups = new LinkedList<String>();
         for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) {
             final String handle = people[personIdx];
@@ -102,51 +111,15 @@
         }
 
         // record the best available data, so far:
-        record.setContactAffinity(affinity);
+        affinityOut[0] = affinity;
 
         if (pendingLookups.isEmpty()) {
             if (INFO) Slog.i(TAG, "final affinity: " + affinity);
             return null;
         }
 
-        if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + record.sbn.getKey());
-        return new RankingReconsideration(record.getKey()) {
-            float mContactAffinity = NONE;
-            @Override
-            public void work() {
-                if (INFO) Slog.i(TAG, "Executing: validation for: " + record.getKey());
-                for (final String handle: pendingLookups) {
-                    LookupResult lookupResult = null;
-                    final Uri uri = Uri.parse(handle);
-                    if ("tel".equals(uri.getScheme())) {
-                        if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
-                        lookupResult = resolvePhoneContact(uri.getSchemeSpecificPart());
-                    } else if ("mailto".equals(uri.getScheme())) {
-                        if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
-                        lookupResult = resolveEmailContact(uri.getSchemeSpecificPart());
-                    } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
-                        if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
-                        lookupResult = searchContacts(uri);
-                    } else {
-                        lookupResult = new LookupResult();  // invalid person for the cache
-                        Slog.w(TAG, "unsupported URI " + handle);
-                    }
-                    if (lookupResult != null) {
-                        synchronized (mPeopleCache) {
-                            mPeopleCache.put(handle, lookupResult);
-                        }
-                        mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
-                    }
-                }
-            }
-
-            @Override
-            public void applyChangesLocked(NotificationRecord operand) {
-                float affinityBound = operand.getContactAffinity();
-                operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
-                if (INFO) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
-            }
-        };
+        if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + key);
+        return new PeopleRankingReconsideration(key, pendingLookups);
     }
 
     // VisibleForTesting
@@ -269,6 +242,19 @@
         // ignore: config has no relevant information yet.
     }
 
+    public float getContactAffinity(Bundle extras) {
+        if (extras == null) return NONE;
+        final String key = Long.toString(System.nanoTime());
+        final float[] affinityOut = new float[1];
+        final PeopleRankingReconsideration prr = validatePeople(key, extras, affinityOut);
+        float affinity = affinityOut[0];
+        if (prr != null) {
+            prr.work();
+            affinity = Math.max(prr.getContactAffinity(), affinity);
+        }
+        return affinity;
+    }
+
     private static class LookupResult {
         private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
         public static final int INVALID_ID = -1;
@@ -328,5 +314,55 @@
             return this;
         }
     }
+
+    private class PeopleRankingReconsideration extends RankingReconsideration {
+        private final LinkedList<String> mPendingLookups;
+
+        private float mContactAffinity = NONE;
+
+        private PeopleRankingReconsideration(String key, LinkedList<String> pendingLookups) {
+            super(key);
+            mPendingLookups = pendingLookups;
+        }
+
+        @Override
+        public void work() {
+            if (INFO) Slog.i(TAG, "Executing: validation for: " + mKey);
+            for (final String handle: mPendingLookups) {
+                LookupResult lookupResult = null;
+                final Uri uri = Uri.parse(handle);
+                if ("tel".equals(uri.getScheme())) {
+                    if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
+                    lookupResult = resolvePhoneContact(uri.getSchemeSpecificPart());
+                } else if ("mailto".equals(uri.getScheme())) {
+                    if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
+                    lookupResult = resolveEmailContact(uri.getSchemeSpecificPart());
+                } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+                    if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
+                    lookupResult = searchContacts(uri);
+                } else {
+                    lookupResult = new LookupResult();  // invalid person for the cache
+                    Slog.w(TAG, "unsupported URI " + handle);
+                }
+                if (lookupResult != null) {
+                    synchronized (mPeopleCache) {
+                        mPeopleCache.put(handle, lookupResult);
+                    }
+                    mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
+                }
+            }
+        }
+
+        @Override
+        public void applyChangesLocked(NotificationRecord operand) {
+            float affinityBound = operand.getContactAffinity();
+            operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
+            if (INFO) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
+        }
+
+        public float getContactAffinity() {
+            return mContactAffinity;
+        }
+    }
 }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 7a5336b..fd35ede 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -34,6 +34,7 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
@@ -189,7 +190,7 @@
     }
 
     private boolean shouldInterceptAudience(NotificationRecord record) {
-        if (!audienceMatches(record)) {
+        if (!audienceMatches(record.getContactAffinity())) {
             ZenLog.traceIntercepted(record, "!audienceMatches");
             return true;
         }
@@ -372,14 +373,27 @@
         return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
     }
 
-    private boolean audienceMatches(NotificationRecord record) {
+    public boolean matchesCallFilter(Bundle extras, ValidateNotificationPeople validator) {
+        final int zen = mZenMode;
+        if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
+        if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+            if (!mConfig.allowCalls) return false; // no calls get through
+            if (validator != null) {
+                final float contactAffinity = validator.getContactAffinity(extras);
+                return audienceMatches(contactAffinity);
+            }
+        }
+        return true;
+    }
+
+    private boolean audienceMatches(float contactAffinity) {
         switch (mConfig.allowFrom) {
             case ZenModeConfig.SOURCE_ANYONE:
                 return true;
             case ZenModeConfig.SOURCE_CONTACT:
-                return record.getContactAffinity() >= ValidateNotificationPeople.VALID_CONTACT;
+                return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
             case ZenModeConfig.SOURCE_STAR:
-                return record.getContactAffinity() >= ValidateNotificationPeople.STARRED_CONTACT;
+                return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
             default:
                 Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
                 return true;
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 694669c..ca11862 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -224,10 +224,6 @@
         }
     }
 
-    public int pruneDexCache(String cacheSubDir) {
-        return mInstaller.execute("prunedexcache " + cacheSubDir);
-    }
-
     public int freeCache(long freeStorageSize) {
         StringBuilder builder = new StringBuilder("freecache");
         builder.append(' ');
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 89878ef..fbaeb37 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1497,29 +1497,6 @@
                 }
             }
 
-            if (didDexOptLibraryOrTool) {
-                // If we dexopted a library or tool, then something on the system has
-                // changed. Consider this significant, and wipe away all other
-                // existing dexopt files to ensure we don't leave any dangling around.
-                //
-                // TODO: This should be revisited because it isn't as good an indicator
-                // as it used to be. It used to include the boot classpath but at some point
-                // DexFile.isDexOptNeeded started returning false for the boot
-                // class path files in all cases. It is very possible in a
-                // small maintenance release update that the library and tool
-                // jars may be unchanged but APK could be removed resulting in
-                // unused dalvik-cache files.
-                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-                    mInstaller.pruneDexCache(dexCodeInstructionSet);
-                }
-
-                // Additionally, delete all dex files from the root directory
-                // since there shouldn't be any there anyway, unless we're upgrading
-                // from an older OS version or a build that contained the "old" style
-                // flat scheme.
-                mInstaller.pruneDexCache(".");
-            }
-
             // Collect vendor overlay packages.
             // (Do this before scanning any apps.)
             // For security and version matching reason, only consider
@@ -7721,8 +7698,21 @@
     public void installPackage(String originPath, IPackageInstallObserver2 observer,
             int installFlags, String installerPackageName, VerificationParams verificationParams,
             String packageAbiOverride) {
+        installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams,
+                packageAbiOverride, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
+            int installFlags, String installerPackageName, VerificationParams verificationParams,
+            String packageAbiOverride, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
                 null);
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "installPackage " + userId);
+        }
 
         final File originFile = new File(originPath);
         final int uid = Binder.getCallingUid();
@@ -7740,7 +7730,7 @@
         if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
             user = UserHandle.ALL;
         } else {
-            user = new UserHandle(UserHandle.getUserId(uid));
+            user = new UserHandle(userId);
         }
 
         final int filteredInstallFlags;
@@ -12988,7 +12978,7 @@
     }
 
     /** Called by UserManagerService */
-    void cleanUpUserLILPw(int userHandle) {
+    void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
         mDirtyUsers.remove(userHandle);
         mSettings.removeUserLPw(userHandle);
         mPendingBroadcasts.remove(userHandle);
@@ -12999,6 +12989,50 @@
             mInstaller.removeUserDataDirs(userHandle);
         }
         mUserNeedsBadging.delete(userHandle);
+        removeUnusedPackagesLILPw(userManager, userHandle);
+    }
+
+    /**
+     * We're removing userHandle and would like to remove any downloaded packages
+     * that are no longer in use by any other user.
+     * @param userHandle the user being removed
+     */
+    private void removeUnusedPackagesLILPw(UserManagerService userManager, final int userHandle) {
+        final boolean DEBUG_CLEAN_APKS = false;
+        int [] users = userManager.getUserIdsLPr();
+        Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
+        while (psit.hasNext()) {
+            PackageSetting ps = psit.next();
+            final String packageName = ps.pkg.packageName;
+            // Skip over if system app
+            if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                continue;
+            }
+            if (DEBUG_CLEAN_APKS) {
+                Slog.i(TAG, "Checking package " + packageName);
+            }
+            boolean keep = false;
+            for (int i = 0; i < users.length; i++) {
+                if (users[i] != userHandle && ps.getInstalled(users[i])) {
+                    keep = true;
+                    if (DEBUG_CLEAN_APKS) {
+                        Slog.i(TAG, "  Keeping package " + packageName + " for user "
+                                + users[i]);
+                    }
+                    break;
+                }
+            }
+            if (!keep) {
+                if (DEBUG_CLEAN_APKS) {
+                    Slog.i(TAG, "  Removing package " + packageName);
+                }
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        deletePackageX(packageName, userHandle, 0);
+                    } //end run
+                });
+            }
+        }
     }
 
     /** Called by UserManagerService */
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8ded7ca..2929939 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -293,6 +293,10 @@
     private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
         UserInfo user = getUserInfoLocked(userId);
         ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+        if (user == null) {
+            // Probably a dying user
+            return users;
+        }
         for (int i = 0; i < mUsers.size(); i++) {
             UserInfo profile = mUsers.valueAt(i);
             if (!isProfileOf(user, profile)) {
@@ -1280,7 +1284,7 @@
 
     private void removeUserStateLocked(final int userHandle) {
         // Cleanup package manager settings
-        mPm.cleanUpUserLILPw(userHandle);
+        mPm.cleanUpUserLILPw(this, userHandle);
 
         // Remove this user from the list
         mUsers.remove(userHandle);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index a49d8ab..d22912c 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -119,7 +119,7 @@
 
         mContext = context;
         mContentResolver = context.getContentResolver();
-        mWatchLogHandler = new WatchLogHandler(IoThread.get().getLooper());
+        mWatchLogHandler = new WatchLogHandler(mContentResolver, IoThread.get().getLooper());
 
         mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
 
@@ -248,7 +248,7 @@
                     serviceState = new ServiceState(component, userId);
                     userState.serviceStateMap.put(component, serviceState);
                 } else {
-                    inputList.addAll(serviceState.mInputList);
+                    inputList.addAll(serviceState.inputList);
                 }
             } else {
                 try {
@@ -258,9 +258,6 @@
                     continue;
                 }
             }
-
-            // Reconnect the service if existing input is updated.
-            updateServiceConnectionLocked(component, userId);
             userState.packageSet.add(si.packageName);
         }
 
@@ -273,7 +270,7 @@
             if (state == null) {
                 state = new TvInputState();
             }
-            state.mInfo = info;
+            state.info = info;
             inputMap.put(info.getId(), state);
         }
 
@@ -285,7 +282,7 @@
 
         for (String inputId : userState.inputMap.keySet()) {
             if (!inputMap.containsKey(inputId)) {
-                TvInputInfo info = userState.inputMap.get(inputId).mInfo;
+                TvInputInfo info = userState.inputMap.get(inputId).info;
                 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
                 if (serviceState != null) {
                     abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
@@ -352,9 +349,9 @@
             }
             // Release created sessions.
             for (SessionState state : userState.sessionStateMap.values()) {
-                if (state.mSession != null) {
+                if (state.session != null) {
                     try {
-                        state.mSession.release();
+                        state.session.release();
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in release", e);
                     }
@@ -364,14 +361,14 @@
 
             // Unregister all callbacks and unbind all services.
             for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                if (serviceState.mCallback != null) {
+                if (serviceState.callback != null) {
                     try {
-                        serviceState.mService.unregisterCallback(serviceState.mCallback);
+                        serviceState.service.unregisterCallback(serviceState.callback);
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in unregisterCallback", e);
                     }
                 }
-                mContext.unbindService(serviceState.mConnection);
+                mContext.unbindService(serviceState.connection);
             }
             userState.serviceStateMap.clear();
 
@@ -412,7 +409,7 @@
             throw new IllegalArgumentException("Session state not found for token " + sessionToken);
         }
         // Only the application that requested this session or the system can access it.
-        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
             throw new SecurityException("Illegal access to the session with token " + sessionToken
                     + " from uid " + callingUid);
         }
@@ -424,10 +421,10 @@
     }
 
     private ITvInputSession getSessionLocked(SessionState sessionState) {
-        ITvInputSession session = sessionState.mSession;
+        ITvInputSession session = sessionState.session;
         if (session == null) {
             throw new IllegalStateException("Session not yet created for token "
-                    + sessionState.mSessionToken);
+                    + sessionState.sessionToken);
         }
         return session;
     }
@@ -439,8 +436,8 @@
     }
 
     private static boolean shouldMaintainConnection(ServiceState serviceState) {
-        return !serviceState.mSessionTokens.isEmpty() || serviceState.mIsHardware;
-        // TODO: Find a way to maintain connection only when necessary.
+        return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
+        // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
     }
 
     private void updateServiceConnectionLocked(ComponentName component, int userId) {
@@ -449,18 +446,18 @@
         if (serviceState == null) {
             return;
         }
-        if (serviceState.mReconnecting) {
-            if (!serviceState.mSessionTokens.isEmpty()) {
+        if (serviceState.reconnecting) {
+            if (!serviceState.sessionTokens.isEmpty()) {
                 // wait until all the sessions are removed.
                 return;
             }
-            serviceState.mReconnecting = false;
+            serviceState.reconnecting = false;
         }
         boolean maintainConnection = shouldMaintainConnection(serviceState);
-        if (serviceState.mService == null && maintainConnection && userId == mCurrentUserId) {
+        if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
             // This means that the service is not yet connected but its state indicates that we
             // have pending requests. Then, connect the service.
-            if (serviceState.mBound) {
+            if (serviceState.bound) {
                 // We have already bound to the service so we don't try to bind again until after we
                 // unbind later on.
                 return;
@@ -470,18 +467,15 @@
             }
 
             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
-            // Binding service may fail if the service is updating.
-            // In that case, the connection will be revived in buildTvInputListLocked called by
-            // onSomePackagesChanged.
-            serviceState.mBound = mContext.bindServiceAsUser(
-                    i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
-        } else if (serviceState.mService != null && !maintainConnection) {
+            serviceState.bound = mContext.bindServiceAsUser(
+                    i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
+        } else if (serviceState.service != null && !maintainConnection) {
             // This means that the service is already connected but its state indicates that we have
             // nothing to do with it. Then, disconnect the service.
             if (DEBUG) {
                 Slog.d(TAG, "unbindService(service=" + component + ")");
             }
-            mContext.unbindService(serviceState.mConnection);
+            mContext.unbindService(serviceState.connection);
             userState.serviceStateMap.remove(component);
         }
     }
@@ -491,19 +485,19 @@
         // Let clients know the create session requests are failed.
         UserState userState = getUserStateLocked(userId);
         List<SessionState> sessionsToAbort = new ArrayList<>();
-        for (IBinder sessionToken : serviceState.mSessionTokens) {
+        for (IBinder sessionToken : serviceState.sessionTokens) {
             SessionState sessionState = userState.sessionStateMap.get(sessionToken);
-            if (sessionState.mSession == null && (inputId == null
-                    || sessionState.mInfo.getId().equals(inputId))) {
+            if (sessionState.session == null && (inputId == null
+                    || sessionState.info.getId().equals(inputId))) {
                 sessionsToAbort.add(sessionState);
             }
         }
         for (SessionState sessionState : sessionsToAbort) {
-            removeSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
-            sendSessionTokenToClientLocked(sessionState.mClient,
-                    sessionState.mInfo.getId(), null, null, sessionState.mSeq);
+            removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
+            sendSessionTokenToClientLocked(sessionState.client,
+                    sessionState.info.getId(), null, null, sessionState.seq);
         }
-        updateServiceConnectionLocked(serviceState.mComponent, userId);
+        updateServiceConnectionLocked(serviceState.component, userId);
     }
 
     private ClientState createClientStateLocked(IBinder clientToken, int userId) {
@@ -518,221 +512,26 @@
         return clientState;
     }
 
-    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
-            final int userId) {
-        final UserState userState = getUserStateLocked(userId);
-        final SessionState sessionState = userState.sessionStateMap.get(sessionToken);
+    private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
+            int userId) {
+        UserState userState = getUserStateLocked(userId);
+        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
         if (DEBUG) {
-            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInfo.getId() + ")");
+            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.info.getId() + ")");
         }
-
-        final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+        InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
 
         // Set up a callback to send the session token.
-        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
-            @Override
-            public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
-                if (DEBUG) {
-                    Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")");
-                }
-                synchronized (mLock) {
-                    sessionState.mSession = session;
-                    sessionState.mHardwareSessionToken = harewareSessionToken;
-                    if (session == null) {
-                        removeSessionStateLocked(sessionToken, userId);
-                        sendSessionTokenToClientLocked(sessionState.mClient,
-                                sessionState.mInfo.getId(), null, null, sessionState.mSeq);
-                    } else {
-                        try {
-                            session.asBinder().linkToDeath(sessionState, 0);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "session process has already died", e);
-                        }
-
-                        IBinder clientToken = sessionState.mClient.asBinder();
-                        ClientState clientState = userState.clientStateMap.get(clientToken);
-                        if (clientState == null) {
-                            clientState = createClientStateLocked(clientToken, userId);
-                        }
-                        clientState.mSessionTokens.add(sessionState.mSessionToken);
-
-                        sendSessionTokenToClientLocked(sessionState.mClient,
-                                sessionState.mInfo.getId(), sessionToken, channels[0],
-                                sessionState.mSeq);
-                    }
-                    channels[0].dispose();
-                }
-            }
-
-            @Override
-            public void onChannelRetuned(Uri channelUri) {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        // TODO: Consider adding this channel change in the watch log. When we do
-                        // that, how we can protect the watch log from malicious tv inputs should
-                        // be addressed. e.g. add a field which represents where the channel change
-                        // originated from.
-                        sessionState.mClient.onChannelRetuned(channelUri, sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onChannelRetuned", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onTracksChanged(List<TvTrackInfo> tracks) {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onTracksChanged(" + tracks + ")");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onTracksChanged(tracks, sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onTracksChanged", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onTrackSelected(int type, String trackId) {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onTrackSelected(type, trackId, sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onTrackSelected", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onVideoAvailable() {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onVideoAvailable()");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onVideoAvailable(sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onVideoAvailable", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onVideoUnavailable(int reason) {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onVideoUnavailable(reason, sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onVideoUnavailable", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onContentAllowed() {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onContentAllowed()");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onContentAllowed(sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onContentAllowed", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onContentBlocked(String rating) {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onContentBlocked()");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onContentBlocked(rating, sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onContentBlocked", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onLayoutSurface(int left, int top, int right, int bottom) {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
-                                + ", right=" + right + ", bottom=" + bottom + ",)");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onLayoutSurface(left, top, right, bottom,
-                                sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onLayoutSurface", e);
-                    }
-                }
-            }
-
-            @Override
-            public void onSessionEvent(String eventType, Bundle eventArgs) {
-                synchronized (mLock) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
-                    }
-                    if (sessionState.mSession == null || sessionState.mClient == null) {
-                        return;
-                    }
-                    try {
-                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
-                                sessionState.mSeq);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in onSessionEvent", e);
-                    }
-                }
-            }
-        };
+        ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
 
         // Create a session. When failed, send a null token immediately.
         try {
-            service.createSession(channels[1], callback, sessionState.mInfo.getId());
+            service.createSession(channels[1], callback, sessionState.info.getId());
         } catch (RemoteException e) {
             Slog.e(TAG, "error in createSession", e);
             removeSessionStateLocked(sessionToken, userId);
-            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null,
-                    null, sessionState.mSeq);
+            sendSessionTokenToClientLocked(sessionState.client, sessionState.info.getId(), null,
+                    null, sessionState.seq);
         }
         channels[1].dispose();
     }
@@ -748,17 +547,17 @@
 
     private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
-        if (sessionState.mSession != null) {
+        if (sessionState.session != null) {
             UserState userState = getUserStateLocked(userId);
             if (sessionToken == userState.mainSessionToken) {
                 setMainLocked(sessionToken, false, callingUid, userId);
             }
             try {
-                sessionState.mSession.release();
+                sessionState.session.release();
             } catch (RemoteException e) {
                 Slog.e(TAG, "session process has already died", e);
             }
-            sessionState.mSession = null;
+            sessionState.session = null;
         }
         removeSessionStateLocked(sessionToken, userId);
     }
@@ -781,22 +580,22 @@
 
         // Also remove the session token from the session token list of the current client and
         // service.
-        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
+        ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
         if (clientState != null) {
-            clientState.mSessionTokens.remove(sessionToken);
+            clientState.sessionTokens.remove(sessionToken);
             if (clientState.isEmpty()) {
-                userState.clientStateMap.remove(sessionState.mClient.asBinder());
+                userState.clientStateMap.remove(sessionState.client.asBinder());
             }
         }
 
-        TvInputInfo info = sessionState.mInfo;
+        TvInputInfo info = sessionState.info;
         if (info != null) {
             ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
             if (serviceState != null) {
-                serviceState.mSessionTokens.remove(sessionToken);
+                serviceState.sessionTokens.remove(sessionToken);
             }
         }
-        updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
+        updateServiceConnectionLocked(sessionState.info.getComponent(), userId);
 
         // Log the end of watch.
         SomeArgs args = SomeArgs.obtain();
@@ -807,13 +606,12 @@
 
     private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
-        if (sessionState.mHardwareSessionToken != null) {
-            sessionState = getSessionStateLocked(sessionState.mHardwareSessionToken,
+        if (sessionState.hardwareSessionToken != null) {
+            sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
                     Process.SYSTEM_UID, userId);
         }
-        ServiceState serviceState = getServiceStateLocked(sessionState.mInfo.getComponent(),
-                userId);
-        if (!serviceState.mIsHardware) {
+        ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
+        if (!serviceState.isHardware) {
             return;
         }
         ITvInputSession session = getSessionLocked(sessionState);
@@ -876,10 +674,10 @@
     private void setStateLocked(String inputId, int state, int userId) {
         UserState userState = getUserStateLocked(userId);
         TvInputState inputState = userState.inputMap.get(inputId);
-        ServiceState serviceState = userState.serviceStateMap.get(inputState.mInfo.getComponent());
-        int oldState = inputState.mState;
-        inputState.mState = state;
-        if (serviceState != null && serviceState.mService == null
+        ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
+        int oldState = inputState.state;
+        inputState.state = state;
+        if (serviceState != null && serviceState.service == null
                 && shouldMaintainConnection(serviceState)) {
             // We don't notify state change while reconnecting. It should remain disconnected.
             return;
@@ -900,7 +698,7 @@
                     UserState userState = getUserStateLocked(resolvedUserId);
                     List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
                     for (TvInputState state : userState.inputMap.values()) {
-                        inputList.add(state.mInfo);
+                        inputList.add(state.info);
                     }
                     return inputList;
                 }
@@ -918,7 +716,7 @@
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(resolvedUserId);
                     TvInputState state = userState.inputMap.get(inputId);
-                    return state == null ? null : state.mInfo;
+                    return state == null ? null : state.info;
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -964,8 +762,8 @@
                         Slog.e(TAG, "client process has already died", e);
                     }
                     for (TvInputState state : userState.inputMap.values()) {
-                        notifyInputStateChangedLocked(userState, state.mInfo.getId(),
-                                state.mState, callback);
+                        notifyInputStateChangedLocked(userState, state.info.getId(), state.state,
+                                callback);
                     }
                 }
             } finally {
@@ -1114,14 +912,14 @@
                         sendSessionTokenToClientLocked(client, inputId, null, null, seq);
                         return;
                     }
-                    TvInputInfo info = inputState.mInfo;
+                    TvInputInfo info = inputState.info;
                     ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
                     if (serviceState == null) {
                         serviceState = new ServiceState(info.getComponent(), resolvedUserId);
                         userState.serviceStateMap.put(info.getComponent(), serviceState);
                     }
                     // Send a null token immediately while reconnecting.
-                    if (serviceState.mReconnecting == true) {
+                    if (serviceState.reconnecting == true) {
                         sendSessionTokenToClientLocked(client, inputId, null, null, seq);
                         return;
                     }
@@ -1135,10 +933,10 @@
                     userState.sessionStateMap.put(sessionToken, sessionState);
 
                     // Also, add them to the session state map of the current service.
-                    serviceState.mSessionTokens.add(sessionToken);
+                    serviceState.sessionTokens.add(sessionToken);
 
-                    if (serviceState.mService != null) {
-                        createSessionInternalLocked(serviceState.mService, sessionToken,
+                    if (serviceState.service != null) {
+                        createSessionInternalLocked(serviceState.service, sessionToken,
                                 resolvedUserId);
                     } else {
                         updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
@@ -1213,10 +1011,10 @@
                     try {
                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 resolvedUserId);
-                        if (sessionState.mHardwareSessionToken == null) {
+                        if (sessionState.hardwareSessionToken == null) {
                             getSessionLocked(sessionState).setSurface(surface);
                         } else {
-                            getSessionLocked(sessionState.mHardwareSessionToken,
+                            getSessionLocked(sessionState.hardwareSessionToken,
                                     Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
                         }
                     } catch (RemoteException e) {
@@ -1244,9 +1042,10 @@
                     try {
                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 resolvedUserId);
-                        getSessionLocked(sessionState).dispatchSurfaceChanged(format, width, height);
-                        if (sessionState.mHardwareSessionToken != null) {
-                            getSessionLocked(sessionState.mHardwareSessionToken, Process.SYSTEM_UID,
+                        getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
+                                height);
+                        if (sessionState.hardwareSessionToken != null) {
+                            getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
                                     resolvedUserId).dispatchSurfaceChanged(format, width, height);
                         }
                     } catch (RemoteException e) {
@@ -1272,10 +1071,10 @@
                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 resolvedUserId);
                         getSessionLocked(sessionState).setVolume(volume);
-                        if (sessionState.mHardwareSessionToken != null) {
+                        if (sessionState.hardwareSessionToken != null) {
                             // Here, we let the hardware session know only whether volume is on or
                             // off to prevent that the volume is controlled in the both side.
-                            getSessionLocked(sessionState.mHardwareSessionToken,
+                            getSessionLocked(sessionState.hardwareSessionToken,
                                     Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
                                             ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
                         }
@@ -1309,7 +1108,7 @@
 
                         // Log the start of watch.
                         SomeArgs args = SomeArgs.obtain();
-                        args.arg1 = sessionState.mInfo.getComponent().getPackageName();
+                        args.arg1 = sessionState.info.getComponent().getPackageName();
                         args.arg2 = System.currentTimeMillis();
                         args.arg3 = ContentUris.parseId(channelUri);
                         args.arg4 = params;
@@ -1569,10 +1368,10 @@
                         return false;
                     }
                     for (SessionState sessionState : userState.sessionStateMap.values()) {
-                        if (sessionState.mInfo.getId().equals(inputId)
-                                && sessionState.mHardwareSessionToken != null) {
+                        if (sessionState.info.getId().equals(inputId)
+                                && sessionState.hardwareSessionToken != null) {
                             hardwareInputId = userState.sessionStateMap.get(
-                                    sessionState.mHardwareSessionToken).mInfo.getId();
+                                    sessionState.hardwareSessionToken).info.getId();
                             break;
                         }
                     }
@@ -1601,8 +1400,8 @@
                         SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
                                 new SessionState[0]);
                         // Check if there is a wrapper input.
-                        if (sessionStates[0].mHardwareSessionToken != null
-                                || sessionStates[1].mHardwareSessionToken != null) {
+                        if (sessionStates[0].hardwareSessionToken != null
+                                || sessionStates[1].hardwareSessionToken != null) {
                             return true;
                         }
                     }
@@ -1662,15 +1461,15 @@
 
                         pw.increaseIndent();
 
-                        pw.println("mSessionTokens:");
+                        pw.println("sessionTokens:");
                         pw.increaseIndent();
-                        for (IBinder token : client.mSessionTokens) {
+                        for (IBinder token : client.sessionTokens) {
                             pw.println("" + token);
                         }
                         pw.decreaseIndent();
 
-                        pw.println("mClientTokens: " + client.mClientToken);
-                        pw.println("mUserId: " + client.mUserId);
+                        pw.println("clientTokens: " + client.clientToken);
+                        pw.println("userId: " + client.userId);
 
                         pw.decreaseIndent();
                     }
@@ -1685,17 +1484,17 @@
 
                         pw.increaseIndent();
 
-                        pw.println("mSessionTokens:");
+                        pw.println("sessionTokens:");
                         pw.increaseIndent();
-                        for (IBinder token : service.mSessionTokens) {
+                        for (IBinder token : service.sessionTokens) {
                             pw.println("" + token);
                         }
                         pw.decreaseIndent();
 
-                        pw.println("mService: " + service.mService);
-                        pw.println("mCallback: " + service.mCallback);
-                        pw.println("mBound: " + service.mBound);
-                        pw.println("mReconnecting: " + service.mReconnecting);
+                        pw.println("service: " + service.service);
+                        pw.println("callback: " + service.callback);
+                        pw.println("bound: " + service.bound);
+                        pw.println("reconnecting: " + service.reconnecting);
 
                         pw.decreaseIndent();
                     }
@@ -1709,15 +1508,15 @@
                         pw.println(entry.getKey() + ": " + session);
 
                         pw.increaseIndent();
-                        pw.println("mInfo: " + session.mInfo);
-                        pw.println("mClient: " + session.mClient);
-                        pw.println("mSeq: " + session.mSeq);
-                        pw.println("mCallingUid: " + session.mCallingUid);
-                        pw.println("mUserId: " + session.mUserId);
-                        pw.println("mSessionToken: " + session.mSessionToken);
-                        pw.println("mSession: " + session.mSession);
-                        pw.println("mLogUri: " + session.mLogUri);
-                        pw.println("mHardwareSessionToken: " + session.mHardwareSessionToken);
+                        pw.println("info: " + session.info);
+                        pw.println("client: " + session.client);
+                        pw.println("seq: " + session.seq);
+                        pw.println("callingUid: " + session.callingUid);
+                        pw.println("userId: " + session.userId);
+                        pw.println("sessionToken: " + session.sessionToken);
+                        pw.println("session: " + session.session);
+                        pw.println("logUri: " + session.logUri);
+                        pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
                         pw.decreaseIndent();
                     }
                     pw.decreaseIndent();
@@ -1736,19 +1535,6 @@
         }
     }
 
-    private static final class TvInputState {
-        // A TvInputInfo object which represents the TV input.
-        private TvInputInfo mInfo;
-
-        // The state of TV input. Connected by default.
-        private int mState = INPUT_STATE_CONNECTED;
-
-        @Override
-        public String toString() {
-            return "mInfo: " + mInfo + "; mState: " + mState;
-        }
-    }
-
     private static final class UserState {
         // A mapping from the TV input id to its TvInputState.
         private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
@@ -1789,104 +1575,117 @@
     }
 
     private final class ClientState implements IBinder.DeathRecipient {
-        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
+        private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
 
-        private IBinder mClientToken;
-        private final int mUserId;
+        private IBinder clientToken;
+        private final int userId;
 
         ClientState(IBinder clientToken, int userId) {
-            mClientToken = clientToken;
-            mUserId = userId;
+            this.clientToken = clientToken;
+            this.userId = userId;
         }
 
         public boolean isEmpty() {
-            return mSessionTokens.isEmpty();
+            return sessionTokens.isEmpty();
         }
 
         @Override
         public void binderDied() {
             synchronized (mLock) {
-                UserState userState = getUserStateLocked(mUserId);
+                UserState userState = getUserStateLocked(userId);
                 // DO NOT remove the client state of clientStateMap in this method. It will be
                 // removed in releaseSessionLocked().
-                ClientState clientState = userState.clientStateMap.get(mClientToken);
+                ClientState clientState = userState.clientStateMap.get(clientToken);
                 if (clientState != null) {
-                    while (clientState.mSessionTokens.size() > 0) {
+                    while (clientState.sessionTokens.size() > 0) {
                         releaseSessionLocked(
-                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
+                                clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
                     }
                 }
-                mClientToken = null;
+                clientToken = null;
             }
         }
     }
 
     private final class ServiceState {
-        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
-        private final ServiceConnection mConnection;
-        private final ComponentName mComponent;
-        private final boolean mIsHardware;
-        private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
+        private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
+        private final ServiceConnection connection;
+        private final ComponentName component;
+        private final boolean isHardware;
+        private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
 
-        private ITvInputService mService;
-        private ServiceCallback mCallback;
-        private boolean mBound;
-        private boolean mReconnecting;
+        private ITvInputService service;
+        private ServiceCallback callback;
+        private boolean bound;
+        private boolean reconnecting;
 
         private ServiceState(ComponentName component, int userId) {
-            mComponent = component;
-            mConnection = new InputServiceConnection(component, userId);
-            mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mComponent);
+            this.component = component;
+            this.connection = new InputServiceConnection(component, userId);
+            this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
+        }
+    }
+
+    private static final class TvInputState {
+        // A TvInputInfo object which represents the TV input.
+        private TvInputInfo info;
+
+        // The state of TV input. Connected by default.
+        private int state = INPUT_STATE_CONNECTED;
+
+        @Override
+        public String toString() {
+            return "info: " + info + "; state: " + state;
         }
     }
 
     private final class SessionState implements IBinder.DeathRecipient {
-        private final TvInputInfo mInfo;
-        private final ITvInputClient mClient;
-        private final int mSeq;
-        private final int mCallingUid;
-        private final int mUserId;
-        private final IBinder mSessionToken;
-        private ITvInputSession mSession;
-        private Uri mLogUri;
+        private final TvInputInfo info;
+        private final ITvInputClient client;
+        private final int seq;
+        private final int callingUid;
+        private final int userId;
+        private final IBinder sessionToken;
+        private ITvInputSession session;
+        private Uri logUri;
         // Not null if this session represents an external device connected to a hardware TV input.
-        private IBinder mHardwareSessionToken;
+        private IBinder hardwareSessionToken;
 
         private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
                 int seq, int callingUid, int userId) {
-            mSessionToken = sessionToken;
-            mInfo = info;
-            mClient = client;
-            mSeq = seq;
-            mCallingUid = callingUid;
-            mUserId = userId;
+            this.sessionToken = sessionToken;
+            this.info = info;
+            this.client = client;
+            this.seq = seq;
+            this.callingUid = callingUid;
+            this.userId = userId;
         }
 
         @Override
         public void binderDied() {
             synchronized (mLock) {
-                mSession = null;
-                if (mClient != null) {
+                session = null;
+                if (client != null) {
                     try {
-                        mClient.onSessionReleased(mSeq);
+                        client.onSessionReleased(seq);
                     } catch(RemoteException e) {
                         Slog.e(TAG, "error in onSessionReleased", e);
                     }
                 }
                 // If there are any other sessions based on this session, they should be released.
-                UserState userState = getUserStateLocked(mUserId);
+                UserState userState = getUserStateLocked(userId);
                 for (SessionState sessionState : userState.sessionStateMap.values()) {
-                    if (mSessionToken == sessionState.mHardwareSessionToken) {
-                        releaseSessionLocked(sessionState.mSessionToken, Process.SYSTEM_UID,
-                                mUserId);
+                    if (sessionToken == sessionState.hardwareSessionToken) {
+                        releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
+                                userId);
                         try {
-                            sessionState.mClient.onSessionReleased(sessionState.mSeq);
+                            sessionState.client.onSessionReleased(sessionState.seq);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "error in onSessionReleased", e);
                         }
                     }
                 }
-                removeSessionStateLocked(mSessionToken, mUserId);
+                removeSessionStateLocked(sessionToken, userId);
             }
         }
     }
@@ -1908,37 +1707,37 @@
             synchronized (mLock) {
                 UserState userState = getUserStateLocked(mUserId);
                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
-                serviceState.mService = ITvInputService.Stub.asInterface(service);
+                serviceState.service = ITvInputService.Stub.asInterface(service);
 
                 // Register a callback, if we need to.
-                if (serviceState.mIsHardware && serviceState.mCallback == null) {
-                    serviceState.mCallback = new ServiceCallback(mComponent, mUserId);
+                if (serviceState.isHardware && serviceState.callback == null) {
+                    serviceState.callback = new ServiceCallback(mComponent, mUserId);
                     try {
-                        serviceState.mService.registerCallback(serviceState.mCallback);
+                        serviceState.service.registerCallback(serviceState.callback);
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in registerCallback", e);
                     }
                 }
 
                 // And create sessions, if any.
-                for (IBinder sessionToken : serviceState.mSessionTokens) {
-                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
+                for (IBinder sessionToken : serviceState.sessionTokens) {
+                    createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
                 }
 
                 for (TvInputState inputState : userState.inputMap.values()) {
-                    if (inputState.mInfo.getComponent().equals(component)
-                            && inputState.mState != INPUT_STATE_DISCONNECTED) {
-                        notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
-                                inputState.mState, null);
+                    if (inputState.info.getComponent().equals(component)
+                            && inputState.state != INPUT_STATE_DISCONNECTED) {
+                        notifyInputStateChangedLocked(userState, inputState.info.getId(),
+                                inputState.state, null);
                     }
                 }
 
-                if (serviceState.mIsHardware) {
+                if (serviceState.isHardware) {
                     List<TvInputHardwareInfo> hardwareInfoList =
                             mTvInputHardwareManager.getHardwareList();
                     for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
                         try {
-                            serviceState.mService.notifyHardwareAdded(hardwareInfo);
+                            serviceState.service.notifyHardwareAdded(hardwareInfo);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "error in notifyHardwareAdded", e);
                         }
@@ -1948,7 +1747,7 @@
                             mTvInputHardwareManager.getHdmiDeviceList();
                     for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
                         try {
-                            serviceState.mService.notifyHdmiDeviceAdded(deviceInfo);
+                            serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
                         }
@@ -1970,16 +1769,16 @@
                 UserState userState = getUserStateLocked(mUserId);
                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
                 if (serviceState != null) {
-                    serviceState.mReconnecting = true;
-                    serviceState.mBound = false;
-                    serviceState.mService = null;
-                    serviceState.mCallback = null;
+                    serviceState.reconnecting = true;
+                    serviceState.bound = false;
+                    serviceState.service = null;
+                    serviceState.callback = null;
 
                     abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
 
                     for (TvInputState inputState : userState.inputMap.values()) {
-                        if (inputState.mInfo.getComponent().equals(component)) {
-                            notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
+                        if (inputState.info.getComponent().equals(component)) {
+                            notifyInputStateChangedLocked(userState, inputState.info.getId(),
                                     INPUT_STATE_DISCONNECTED, null);
                         }
                     }
@@ -2012,7 +1811,7 @@
 
         private void addTvInputLocked(TvInputInfo inputInfo) {
             ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
-            serviceState.mInputList.add(inputInfo);
+            serviceState.inputList.add(inputInfo);
             buildTvInputListLocked(mUserId);
         }
 
@@ -2042,7 +1841,7 @@
             synchronized (mLock) {
                 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
                 boolean removed = false;
-                for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
+                for (Iterator<TvInputInfo> it = serviceState.inputList.iterator();
                         it.hasNext(); ) {
                     if (it.next().getId().equals(inputId)) {
                         it.remove();
@@ -2060,7 +1859,211 @@
         }
     }
 
-    private final class WatchLogHandler extends Handler {
+    private final class SessionCallback extends ITvInputSessionCallback.Stub {
+        private final SessionState sessionState;
+        private final InputChannel[] mChannels;
+
+        SessionCallback(SessionState sessionState, InputChannel[] channels) {
+            this.sessionState = sessionState;
+            mChannels = channels;
+        }
+
+        @Override
+        public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
+            if (DEBUG) {
+                Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.info.getId() + ")");
+            }
+            synchronized (mLock) {
+                sessionState.session = session;
+                sessionState.hardwareSessionToken = harewareSessionToken;
+                if (session == null) {
+                    removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
+                    sendSessionTokenToClientLocked(sessionState.client,
+                            sessionState.info.getId(), null, null, sessionState.seq);
+                } else {
+                    try {
+                        session.asBinder().linkToDeath(sessionState, 0);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "session process has already died", e);
+                    }
+
+                    IBinder clientToken = sessionState.client.asBinder();
+                    UserState userState = getUserStateLocked(sessionState.userId);
+                    ClientState clientState = userState.clientStateMap.get(clientToken);
+                    if (clientState == null) {
+                        clientState = createClientStateLocked(clientToken, sessionState.userId);
+                    }
+                    clientState.sessionTokens.add(sessionState.sessionToken);
+
+                    sendSessionTokenToClientLocked(sessionState.client,
+                            sessionState.info.getId(), sessionState.sessionToken, mChannels[0],
+                            sessionState.seq);
+                }
+                mChannels[0].dispose();
+            }
+        }
+
+        @Override
+        public void onChannelRetuned(Uri channelUri) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    // TODO: Consider adding this channel change in the watch log. When we do
+                    // that, how we can protect the watch log from malicious tv inputs should
+                    // be addressed. e.g. add a field which represents where the channel change
+                    // originated from.
+                    sessionState.client.onChannelRetuned(channelUri, sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onChannelRetuned", e);
+                }
+            }
+        }
+
+        @Override
+        public void onTracksChanged(List<TvTrackInfo> tracks) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onTracksChanged(" + tracks + ")");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onTracksChanged(tracks, sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onTracksChanged", e);
+                }
+            }
+        }
+
+        @Override
+        public void onTrackSelected(int type, String trackId) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onTrackSelected(type, trackId, sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onTrackSelected", e);
+                }
+            }
+        }
+
+        @Override
+        public void onVideoAvailable() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onVideoAvailable()");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onVideoAvailable(sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onVideoAvailable", e);
+                }
+            }
+        }
+
+        @Override
+        public void onVideoUnavailable(int reason) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onVideoUnavailable(reason, sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onVideoUnavailable", e);
+                }
+            }
+        }
+
+        @Override
+        public void onContentAllowed() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onContentAllowed()");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onContentAllowed(sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onContentAllowed", e);
+                }
+            }
+        }
+
+        @Override
+        public void onContentBlocked(String rating) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onContentBlocked()");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onContentBlocked(rating, sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onContentBlocked", e);
+                }
+            }
+        }
+
+        @Override
+        public void onLayoutSurface(int left, int top, int right, int bottom) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
+                            + ", right=" + right + ", bottom=" + bottom + ",)");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onLayoutSurface(left, top, right, bottom, sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onLayoutSurface", e);
+                }
+            }
+        }
+
+        @Override
+        public void onSessionEvent(String eventType, Bundle eventArgs) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
+                }
+                if (sessionState.session == null || sessionState.client == null) {
+                    return;
+                }
+                try {
+                    sessionState.client.onSessionEvent(eventType, eventArgs,
+                            sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onSessionEvent", e);
+                }
+            }
+        }
+    }
+
+    private static final class WatchLogHandler extends Handler {
         // There are only two kinds of watch events that can happen on the system:
         // 1. The current TV input session is tuned to a new channel.
         // 2. The session is released for some reason.
@@ -2072,8 +2075,11 @@
         private static final int MSG_LOG_WATCH_START = 1;
         private static final int MSG_LOG_WATCH_END = 2;
 
-        public WatchLogHandler(Looper looper) {
+        private final ContentResolver mContentResolver;
+
+        public WatchLogHandler(ContentResolver contentResolver, Looper looper) {
             super(looper);
+            mContentResolver = contentResolver;
         }
 
         @Override
@@ -2159,7 +2165,7 @@
         }
     }
 
-    final class HardwareListener implements TvInputHardwareManager.Listener {
+    private final class HardwareListener implements TvInputHardwareManager.Listener {
         @Override
         public void onStateChanged(String inputId, int state) {
             synchronized (mLock) {
@@ -2173,9 +2179,9 @@
                 UserState userState = getUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+                    if (!serviceState.isHardware || serviceState.service == null) continue;
                     try {
-                        serviceState.mService.notifyHardwareAdded(info);
+                        serviceState.service.notifyHardwareAdded(info);
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHardwareAdded", e);
                     }
@@ -2189,9 +2195,9 @@
                 UserState userState = getUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+                    if (!serviceState.isHardware || serviceState.service == null) continue;
                     try {
-                        serviceState.mService.notifyHardwareRemoved(info);
+                        serviceState.service.notifyHardwareRemoved(info);
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHardwareRemoved", e);
                     }
@@ -2205,9 +2211,9 @@
                 UserState userState = getUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+                    if (!serviceState.isHardware || serviceState.service == null) continue;
                     try {
-                        serviceState.mService.notifyHdmiDeviceAdded(deviceInfo);
+                        serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
                     }
@@ -2221,9 +2227,9 @@
                 UserState userState = getUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+                    if (!serviceState.isHardware || serviceState.service == null) continue;
                     try {
-                        serviceState.mService.notifyHdmiDeviceRemoved(deviceInfo);
+                        serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
                     }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index ef74205..69c9144 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -114,6 +114,10 @@
         transformation.clear();
         transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
         hasTransformation = true;
+
+        if (!mAppToken.appFullscreen) {
+            anim.setBackgroundColor(0);
+        }
     }
 
     public void setDummyAnimation() {
diff --git a/telecomm/java/android/telecomm/PhoneAccount.java b/telecomm/java/android/telecomm/PhoneAccount.java
index 411f48c..97a7b9d 100644
--- a/telecomm/java/android/telecomm/PhoneAccount.java
+++ b/telecomm/java/android/telecomm/PhoneAccount.java
@@ -93,7 +93,7 @@
         private CharSequence mLabel;
         private CharSequence mShortDescription;
 
-        private Builder() {}
+        public Builder() {}
 
         public Builder withAccountHandle(PhoneAccountHandle value) {
             this.mAccountHandle = value;
diff --git a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
index 42c77d7..0ab7564 100644
--- a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
+++ b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
@@ -20,6 +20,8 @@
 
 /**
  * Simple response callback object.
+ *
+ * {@hide}
  */
 oneway interface RemoteServiceCallback {
     void onError();
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index b6591bd..c08c1a3 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.text.format.DateUtils;
-import android.util.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -36,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Map;
 
 public class UsageStatsActivity extends ListActivity {
     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
@@ -84,7 +84,7 @@
     private void updateAdapter() {
         long now = System.currentTimeMillis();
         long beginTime = now - USAGE_STATS_PERIOD;
-        ArrayMap<String, UsageStats> stats = mUsageStatsManager.queryAndAggregateUsageStats(
+        Map<String, UsageStats> stats = mUsageStatsManager.queryAndAggregateUsageStats(
                 beginTime, now);
         mAdapter.update(stats);
     }
@@ -92,17 +92,13 @@
     private class Adapter extends BaseAdapter {
         private ArrayList<UsageStats> mStats = new ArrayList<>();
 
-        public void update(ArrayMap<String, UsageStats> stats) {
+        public void update(Map<String, UsageStats> stats) {
             mStats.clear();
             if (stats == null) {
                 return;
             }
 
-            final int packageCount = stats.size();
-            for (int i = 0; i < packageCount; i++) {
-                mStats.add(stats.valueAt(i));
-            }
-
+            mStats.addAll(stats.values());
             Collections.sort(mStats, mComparator);
             notifyDataSetChanged();
         }
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index e1a579c..1f01461 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -92,8 +92,7 @@
                 break;
             case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNENROLLED:
                 Log.i(TAG, "STATE_KEYPHRASE_UNENROLLED");
-                Intent enroll = mHotwordDetector.getManageIntent(
-                        AlwaysOnHotwordDetector.MANAGE_ACTION_ENROLL);
+                Intent enroll = mHotwordDetector.createIntentToEnroll();
                 Log.i(TAG, "Need to enroll with " + enroll);
                 break;
             case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index b44e2d1..117fc24 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1594,6 +1594,11 @@
     return mIncludedAssets.getResources(false);
 }
 
+AssetManager& AaptAssets::getAssetManager()
+{
+    return mIncludedAssets;
+}
+
 void AaptAssets::print(const String8& prefix) const
 {
     String8 innerPrefix(prefix);
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index 0c2576a..3fc9f81 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -561,6 +561,7 @@
     status_t buildIncludedResources(Bundle* bundle);
     status_t addIncludedResources(const sp<AaptFile>& file);
     const ResTable& getIncludedResources() const;
+    AssetManager& getAssetManager();
 
     void print(const String8& prefix) const;
 
diff --git a/tools/aapt/AaptXml.cpp b/tools/aapt/AaptXml.cpp
new file mode 100644
index 0000000..708e405
--- /dev/null
+++ b/tools/aapt/AaptXml.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+#include <utils/String8.h>
+
+#include "AaptXml.h"
+
+using namespace android;
+
+namespace AaptXml {
+
+static String8 getStringAttributeAtIndex(const ResXMLTree& tree, ssize_t attrIndex,
+        String8* outError) {
+    Res_value value;
+    if (tree.getAttributeValue(attrIndex, &value) < 0) {
+        if (outError != NULL) {
+            *outError = "could not find attribute at index";
+        }
+        return String8();
+    }
+
+    if (value.dataType != Res_value::TYPE_STRING) {
+        if (outError != NULL) {
+            *outError = "attribute is not a string value";
+        }
+        return String8();
+    }
+
+    size_t len;
+    const uint16_t* str = tree.getAttributeStringValue(attrIndex, &len);
+    return str ? String8(str, len) : String8();
+}
+
+static int32_t getIntegerAttributeAtIndex(const ResXMLTree& tree, ssize_t attrIndex,
+    int32_t defValue, String8* outError) {
+    Res_value value;
+    if (tree.getAttributeValue(attrIndex, &value) < 0) {
+        if (outError != NULL) {
+            *outError = "could not find attribute at index";
+        }
+        return defValue;
+    }
+
+    if (value.dataType < Res_value::TYPE_FIRST_INT
+            || value.dataType > Res_value::TYPE_LAST_INT) {
+        if (outError != NULL) {
+            *outError = "attribute is not an integer value";
+        }
+        return defValue;
+    }
+    return value.data;
+}
+
+
+ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) {
+    size_t attrCount = tree.getAttributeCount();
+    for (size_t i = 0; i < attrCount; i++) {
+        if (tree.getAttributeNameResID(i) == attrRes) {
+            return (ssize_t)i;
+        }
+    }
+    return -1;
+}
+
+String8 getAttribute(const ResXMLTree& tree, const char* ns,
+        const char* attr, String8* outError) {
+    ssize_t idx = tree.indexOfAttribute(ns, attr);
+    if (idx < 0) {
+        return String8();
+    }
+    return getStringAttributeAtIndex(tree, idx, outError);
+}
+
+String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError) {
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return String8();
+    }
+    return getStringAttributeAtIndex(tree, idx, outError);
+}
+
+String8 getResolvedAttribute(const ResTable& resTable, const ResXMLTree& tree,
+        uint32_t attrRes, String8* outError) {
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return String8();
+    }
+    Res_value value;
+    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+        if (value.dataType == Res_value::TYPE_STRING) {
+            size_t len;
+            const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+            return str ? String8(str, len) : String8();
+        }
+        resTable.resolveReference(&value, 0);
+        if (value.dataType != Res_value::TYPE_STRING) {
+            if (outError != NULL) {
+                *outError = "attribute is not a string value";
+            }
+            return String8();
+        }
+    }
+    size_t len;
+    const Res_value* value2 = &value;
+    const char16_t* str = resTable.valueToString(value2, 0, NULL, &len);
+    return str ? String8(str, len) : String8();
+}
+
+int32_t getIntegerAttribute(const ResXMLTree& tree, const char* ns,
+        const char* attr, int32_t defValue, String8* outError) {
+    ssize_t idx = tree.indexOfAttribute(ns, attr);
+    if (idx < 0) {
+        return defValue;
+    }
+    return getIntegerAttributeAtIndex(tree, idx, defValue, outError);
+}
+
+int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, int32_t defValue,
+        String8* outError) {
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return defValue;
+    }
+    return getIntegerAttributeAtIndex(tree, idx, defValue, outError);
+}
+
+int32_t getResolvedIntegerAttribute(const ResTable& resTable, const ResXMLTree& tree,
+        uint32_t attrRes, int32_t defValue, String8* outError) {
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return defValue;
+    }
+    Res_value value;
+    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+        if (value.dataType == Res_value::TYPE_REFERENCE) {
+            resTable.resolveReference(&value, 0);
+        }
+        if (value.dataType < Res_value::TYPE_FIRST_INT
+                || value.dataType > Res_value::TYPE_LAST_INT) {
+            if (outError != NULL) {
+                *outError = "attribute is not an integer value";
+            }
+            return defValue;
+        }
+    }
+    return value.data;
+}
+
+void getResolvedResourceAttribute(const ResTable& resTable, const ResXMLTree& tree,
+        uint32_t attrRes, Res_value* outValue, String8* outError) {
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        if (outError != NULL) {
+            *outError = "attribute could not be found";
+        }
+        return;
+    }
+    if (tree.getAttributeValue(idx, outValue) != NO_ERROR) {
+        if (outValue->dataType == Res_value::TYPE_REFERENCE) {
+            resTable.resolveReference(outValue, 0);
+        }
+        // The attribute was found and was resolved if need be.
+        return;
+    }
+    if (outError != NULL) {
+        *outError = "error getting resolved resource attribute";
+    }
+}
+
+} // namespace AaptXml
diff --git a/tools/aapt/AaptXml.h b/tools/aapt/AaptXml.h
new file mode 100644
index 0000000..16977f3
--- /dev/null
+++ b/tools/aapt/AaptXml.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef __AAPT_XML_H
+#define __AAPT_XML_H
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/String8.h>
+
+/**
+ * Utility methods for dealing with ResXMLTree.
+ */
+namespace AaptXml {
+
+/**
+ * Returns the index of the attribute, or < 0 if it was not found.
+ */
+ssize_t indexOfAttribute(const android::ResXMLTree& tree, uint32_t attrRes);
+
+/**
+ * Returns the string value for the specified attribute.
+ * The string must be present in the ResXMLTree's string pool (inline in the XML).
+ */
+android::String8 getAttribute(const android::ResXMLTree& tree, const char* ns,
+        const char* attr, android::String8* outError = NULL);
+
+/**
+ * Returns the string value for the specified attribute, or an empty string
+ * if the attribute does not exist.
+ * The string must be present in the ResXMLTree's string pool (inline in the XML).
+ */
+android::String8 getAttribute(const android::ResXMLTree& tree, uint32_t attrRes,
+        android::String8* outError = NULL);
+
+/**
+ * Returns the integer value for the specified attribute, or the default value
+ * if the attribute does not exist.
+ * The integer must be declared inline in the XML.
+ */
+int32_t getIntegerAttribute(const android::ResXMLTree& tree, const char* ns,
+        const char* attr, int32_t defValue = -1, android::String8* outError = NULL);
+
+/**
+ * Returns the integer value for the specified attribute, or the default value
+ * if the attribute does not exist.
+ * The integer must be declared inline in the XML.
+ */
+inline int32_t getIntegerAttribute(const android::ResXMLTree& tree, const char* ns,
+        const char* attr, android::String8* outError) {
+    return getIntegerAttribute(tree, ns, attr, -1, outError);
+}
+
+/**
+ * Returns the integer value for the specified attribute, or the default value
+ * if the attribute does not exist.
+ * The integer must be declared inline in the XML.
+ */
+int32_t getIntegerAttribute(const android::ResXMLTree& tree, uint32_t attrRes,
+        int32_t defValue = -1, android::String8* outError = NULL);
+
+/**
+ * Returns the integer value for the specified attribute, or the default value
+ * if the attribute does not exist.
+ * The integer must be declared inline in the XML.
+ */
+inline int32_t getIntegerAttribute(const android::ResXMLTree& tree, uint32_t attrRes,
+        android::String8* outError) {
+    return getIntegerAttribute(tree, attrRes, -1, outError);
+}
+
+/**
+ * Returns the integer value for the specified attribute, or the default value
+ * if the attribute does not exist.
+ * The integer may be a resource in the supplied ResTable.
+ */
+int32_t getResolvedIntegerAttribute(const android::ResTable& resTable,
+        const android::ResXMLTree& tree, uint32_t attrRes, int32_t defValue = -1,
+        android::String8* outError = NULL);
+
+/**
+ * Returns the integer value for the specified attribute, or the default value
+ * if the attribute does not exist.
+ * The integer may be a resource in the supplied ResTable.
+ */
+inline int32_t getResolvedIntegerAttribute(const android::ResTable& resTable,
+        const android::ResXMLTree& tree, uint32_t attrRes,
+        android::String8* outError) {
+    return getResolvedIntegerAttribute(resTable, tree, attrRes, -1, outError);
+}
+
+/**
+ * Returns the string value for the specified attribute, or an empty string
+ * if the attribute does not exist.
+ * The string may be a resource in the supplied ResTable.
+ */
+android::String8 getResolvedAttribute(const android::ResTable& resTable,
+        const android::ResXMLTree& tree, uint32_t attrRes,
+        android::String8* outError = NULL);
+
+/**
+ * Returns the resource for the specified attribute in the outValue parameter.
+ * The resource may be a resource in the supplied ResTable.
+ */
+void getResolvedResourceAttribute(const android::ResTable& resTable,
+        const android::ResXMLTree& tree, uint32_t attrRes, android::Res_value* outValue,
+        android::String8* outError = NULL);
+
+} // namespace AaptXml
+
+#endif // __AAPT_XML_H
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index 4ce5045..2cbabe1 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -28,6 +28,7 @@
     AaptAssets.cpp \
     AaptConfig.cpp \
     AaptUtil.cpp \
+    AaptXml.cpp \
     ApkBuilder.cpp \
     Command.cpp \
     CrunchCache.cpp \
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index af49461..9bed899 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -130,6 +130,10 @@
     void setErrorOnFailedInsert(bool val) { mErrorOnFailedInsert = val; }
     bool getErrorOnMissingConfigEntry() { return mErrorOnMissingConfigEntry; }
     void setErrorOnMissingConfigEntry(bool val) { mErrorOnMissingConfigEntry = val; }
+    const android::String8& getPlatformBuildVersionCode() { return mPlatformVersionCode; }
+    void setPlatformBuildVersionCode(const android::String8& code) { mPlatformVersionCode = code; }
+    const android::String8& getPlatformBuildVersionName() { return mPlatformVersionName; }
+    void setPlatformBuildVersionName(const android::String8& name) { mPlatformVersionName = name; }
 
     bool getUTF16StringsOption() {
         return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO);
@@ -323,6 +327,8 @@
     const char* mSingleCrunchInputFile;
     const char* mSingleCrunchOutputFile;
     bool        mBuildSharedLibrary;
+    android::String8 mPlatformVersionCode;
+    android::String8 mPlatformVersionName;
 
     /* file specification */
     int         mArgc;
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index a0f0a08..27e60f3 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -3,6 +3,7 @@
 //
 // Android Asset Packaging Tool main entry point.
 //
+#include "AaptXml.h"
 #include "ApkBuilder.h"
 #include "Bundle.h"
 #include "Images.h"
@@ -241,162 +242,17 @@
     return result;
 }
 
-static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes)
-{
-    size_t N = tree.getAttributeCount();
-    for (size_t i=0; i<N; i++) {
-        if (tree.getAttributeNameResID(i) == attrRes) {
-            return (ssize_t)i;
-        }
-    }
-    return -1;
-}
-
-String8 getAttribute(const ResXMLTree& tree, const char* ns,
-                            const char* attr, String8* outError)
-{
-    ssize_t idx = tree.indexOfAttribute(ns, attr);
-    if (idx < 0) {
-        return String8();
-    }
-    Res_value value;
-    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
-        if (value.dataType != Res_value::TYPE_STRING) {
-            if (outError != NULL) {
-                *outError = "attribute is not a string value";
-            }
-            return String8();
-        }
-    }
-    size_t len;
-    const uint16_t* str = tree.getAttributeStringValue(idx, &len);
-    return str ? String8(str, len) : String8();
-}
-
-static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
-{
-    ssize_t idx = indexOfAttribute(tree, attrRes);
-    if (idx < 0) {
-        return String8();
-    }
-    Res_value value;
-    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
-        if (value.dataType != Res_value::TYPE_STRING) {
-            if (outError != NULL) {
-                *outError = "attribute is not a string value";
-            }
-            return String8();
-        }
-    }
-    size_t len;
-    const uint16_t* str = tree.getAttributeStringValue(idx, &len);
-    return str ? String8(str, len) : String8();
-}
-
-static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes,
-        String8* outError, int32_t defValue = -1)
-{
-    ssize_t idx = indexOfAttribute(tree, attrRes);
-    if (idx < 0) {
-        return defValue;
-    }
-    Res_value value;
-    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
-        if (value.dataType < Res_value::TYPE_FIRST_INT
-                || value.dataType > Res_value::TYPE_LAST_INT) {
-            if (outError != NULL) {
-                *outError = "attribute is not an integer value";
-            }
-            return defValue;
-        }
-    }
-    return value.data;
-}
-
-static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree,
-        uint32_t attrRes, String8* outError, int32_t defValue = -1)
-{
-    ssize_t idx = indexOfAttribute(tree, attrRes);
-    if (idx < 0) {
-        return defValue;
-    }
-    Res_value value;
-    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
-        if (value.dataType == Res_value::TYPE_REFERENCE) {
-            resTable->resolveReference(&value, 0);
-        }
-        if (value.dataType < Res_value::TYPE_FIRST_INT
-                || value.dataType > Res_value::TYPE_LAST_INT) {
-            if (outError != NULL) {
-                *outError = "attribute is not an integer value";
-            }
-            return defValue;
-        }
-    }
-    return value.data;
-}
-
-static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree,
-        uint32_t attrRes, String8* outError)
-{
-    ssize_t idx = indexOfAttribute(tree, attrRes);
-    if (idx < 0) {
-        return String8();
-    }
-    Res_value value;
-    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
-        if (value.dataType == Res_value::TYPE_STRING) {
-            size_t len;
-            const uint16_t* str = tree.getAttributeStringValue(idx, &len);
-            return str ? String8(str, len) : String8();
-        }
-        resTable->resolveReference(&value, 0);
-        if (value.dataType != Res_value::TYPE_STRING) {
-            if (outError != NULL) {
-                *outError = "attribute is not a string value";
-            }
-            return String8();
-        }
-    }
-    size_t len;
-    const Res_value* value2 = &value;
-    const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len);
-    return str ? String8(str, len) : String8();
-}
-
-static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable,
-        const ResXMLTree& tree, uint32_t attrRes, String8* outError)
-{
-    ssize_t idx = indexOfAttribute(tree, attrRes);
-    if (idx < 0) {
-        if (outError != NULL) {
-            *outError = "attribute could not be found";
-        }
-        return;
-    }
-    if (tree.getAttributeValue(idx, value) != NO_ERROR) {
-        if (value->dataType == Res_value::TYPE_REFERENCE) {
-            resTable->resolveReference(value, 0);
-        }
-        // The attribute was found and was resolved if need be.
-        return;
-    }
-    if (outError != NULL) {
-        *outError = "error getting resolved resource attribute";
-    }
-}
-
-static void printResolvedResourceAttribute(const ResTable* resTable, const ResXMLTree& tree,
+static void printResolvedResourceAttribute(const ResTable& resTable, const ResXMLTree& tree,
         uint32_t attrRes, String8 attrLabel, String8* outError)
 {
     Res_value value;
-    getResolvedResourceAttribute(&value, resTable, tree, attrRes, outError);
+    AaptXml::getResolvedResourceAttribute(resTable, tree, attrRes, &value, outError);
     if (*outError != "") {
         *outError = "error print resolved resource attribute";
         return;
     }
     if (value.dataType == Res_value::TYPE_STRING) {
-        String8 result = getResolvedAttribute(resTable, tree, attrRes, outError);
+        String8 result = AaptXml::getResolvedAttribute(resTable, tree, attrRes, outError);
         printf("%s='%s'", attrLabel.string(),
                 ResTable::normalizeForOutput(result.string()).string());
     } else if (Res_value::TYPE_FIRST_INT <= value.dataType &&
@@ -488,10 +344,10 @@
         }
         String8 tag(ctag16);
         if (tag == "screen") {
-            int32_t screenSize = getIntegerAttribute(tree,
-                    SCREEN_SIZE_ATTR, NULL, -1);
-            int32_t screenDensity = getIntegerAttribute(tree,
-                    SCREEN_DENSITY_ATTR, NULL, -1);
+            int32_t screenSize = AaptXml::getIntegerAttribute(tree,
+                    SCREEN_SIZE_ATTR);
+            int32_t screenDensity = AaptXml::getIntegerAttribute(tree,
+                    SCREEN_DENSITY_ATTR);
             if (screenSize > 0 && screenDensity > 0) {
                 if (!first) {
                     printf(",");
@@ -577,7 +433,7 @@
                 }
             } else if (depth == 2 && withinApduService) {
                 if (tag == "aid-group") {
-                    String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
+                    String8 category = AaptXml::getAttribute(tree, CATEGORY_ATTR, &error);
                     if (error != "") {
                         if (outError != NULL) *outError = error;
                         return Vector<String8>();
@@ -876,11 +732,11 @@
                         fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
                         goto bail;
                     }
-                    String8 pkg = getAttribute(tree, NULL, "package", NULL);
+                    String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL);
                     printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
                 } else if (depth == 2 && tag == "permission") {
                     String8 error;
-                    String8 name = getAttribute(tree, NAME_ATTR, &error);
+                    String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                     if (error != "") {
                         fprintf(stderr, "ERROR: %s\n", error.string());
                         goto bail;
@@ -889,14 +745,14 @@
                             ResTable::normalizeForOutput(name.string()).string());
                 } else if (depth == 2 && tag == "uses-permission") {
                     String8 error;
-                    String8 name = getAttribute(tree, NAME_ATTR, &error);
+                    String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                     if (error != "") {
                         fprintf(stderr, "ERROR: %s\n", error.string());
                         goto bail;
                     }
                     printUsesPermission(name,
-                            getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
-                            getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
+                            AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
+                            AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
                 }
             }
         } else if (strcmp("badging", option) == 0) {
@@ -1151,12 +1007,14 @@
                         fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
                         goto bail;
                     }
-                    pkg = getAttribute(tree, NULL, "package", NULL);
+                    pkg = AaptXml::getAttribute(tree, NULL, "package", NULL);
                     printf("package: name='%s' ",
                             ResTable::normalizeForOutput(pkg.string()).string());
-                    int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
+                    int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR,
+                            &error);
                     if (error != "") {
-                        fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
+                        fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n",
+                                error.string());
                         goto bail;
                     }
                     if (versionCode > 0) {
@@ -1164,23 +1022,29 @@
                     } else {
                         printf("versionCode='' ");
                     }
-                    String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
+                    String8 versionName = AaptXml::getResolvedAttribute(res, tree,
+                            VERSION_NAME_ATTR, &error);
                     if (error != "") {
-                        fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
+                        fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n",
+                                error.string());
                         goto bail;
                     }
                     printf("versionName='%s'",
                             ResTable::normalizeForOutput(versionName.string()).string());
 
-                    String8 splitName = getAttribute(tree, NULL, "split", NULL);
+                    String8 splitName = AaptXml::getAttribute(tree, NULL, "split");
                     if (!splitName.isEmpty()) {
                         printf(" split='%s'", ResTable::normalizeForOutput(
                                     splitName.string()).string());
                     }
+
+                    String8 platformVersionName = AaptXml::getAttribute(tree, NULL,
+                            "platformBuildVersionName");
+                    printf(" platformBuildVersionName='%s'", platformVersionName.string());
                     printf("\n");
 
-                    int32_t installLocation = getResolvedIntegerAttribute(&res, tree,
-                            INSTALL_LOCATION_ATTR, &error, -1);
+                    int32_t installLocation = AaptXml::getResolvedIntegerAttribute(res, tree,
+                            INSTALL_LOCATION_ATTR, &error);
                     if (error != "") {
                         fprintf(stderr, "ERROR getting 'android:installLocation' attribute: %s\n",
                                 error.string());
@@ -1215,7 +1079,8 @@
                         for (size_t i=0; i<NL; i++) {
                             const char* localeStr =  locales[i].string();
                             assets.setLocale(localeStr != NULL ? localeStr : "");
-                            String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+                            String8 llabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR,
+                                    &error);
                             if (llabel != "") {
                                 if (localeStr == NULL || strlen(localeStr) == 0) {
                                     label = llabel;
@@ -1236,7 +1101,8 @@
                         for (size_t i=0; i<ND; i++) {
                             tmpConfig.density = densities[i];
                             assets.setConfiguration(tmpConfig);
-                            String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+                            String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR,
+                                    &error);
                             if (icon != "") {
                                 printf("application-icon-%d:'%s'\n", densities[i],
                                         ResTable::normalizeForOutput(icon.string()).string());
@@ -1244,14 +1110,17 @@
                         }
                         assets.setConfiguration(config);
 
-                        String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+                        String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, &error);
                         if (error != "") {
-                            fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
+                            fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
+                                    error.string());
                             goto bail;
                         }
-                        int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
+                        int32_t testOnly = AaptXml::getIntegerAttribute(tree, TEST_ONLY_ATTR, 0,
+                                &error);
                         if (error != "") {
-                            fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
+                            fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n",
+                                    error.string());
                             goto bail;
                         }
                         printf("application: label='%s' ",
@@ -1261,9 +1130,11 @@
                             printf("testOnly='%d'\n", testOnly);
                         }
 
-                        int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
+                        int32_t debuggable = AaptXml::getResolvedIntegerAttribute(res, tree,
+                                DEBUGGABLE_ATTR, 0, &error);
                         if (error != "") {
-                            fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
+                            fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n",
+                                    error.string());
                             goto bail;
                         }
                         if (debuggable != 0) {
@@ -1284,10 +1155,11 @@
                             }
                         }
                     } else if (tag == "uses-sdk") {
-                        int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
+                        int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
                         if (error != "") {
                             error = "";
-                            String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
+                            String8 name = AaptXml::getResolvedAttribute(res, tree,
+                                    MIN_SDK_VERSION_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
                                         error.string());
@@ -1300,14 +1172,15 @@
                             targetSdk = code;
                             printf("sdkVersion:'%d'\n", code);
                         }
-                        code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
+                        code = AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR);
                         if (code != -1) {
                             printf("maxSdkVersion:'%d'\n", code);
                         }
-                        code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
+                        code = AaptXml::getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
                         if (error != "") {
                             error = "";
-                            String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
+                            String8 name = AaptXml::getResolvedAttribute(res, tree,
+                                    TARGET_SDK_VERSION_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
                                         error.string());
@@ -1323,16 +1196,16 @@
                             printf("targetSdkVersion:'%d'\n", code);
                         }
                     } else if (tag == "uses-configuration") {
-                        int32_t reqTouchScreen = getIntegerAttribute(tree,
-                                REQ_TOUCH_SCREEN_ATTR, NULL, 0);
-                        int32_t reqKeyboardType = getIntegerAttribute(tree,
-                                REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
-                        int32_t reqHardKeyboard = getIntegerAttribute(tree,
-                                REQ_HARD_KEYBOARD_ATTR, NULL, 0);
-                        int32_t reqNavigation = getIntegerAttribute(tree,
-                                REQ_NAVIGATION_ATTR, NULL, 0);
-                        int32_t reqFiveWayNav = getIntegerAttribute(tree,
-                                REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
+                        int32_t reqTouchScreen = AaptXml::getIntegerAttribute(tree,
+                                REQ_TOUCH_SCREEN_ATTR, 0);
+                        int32_t reqKeyboardType = AaptXml::getIntegerAttribute(tree,
+                                REQ_KEYBOARD_TYPE_ATTR, 0);
+                        int32_t reqHardKeyboard = AaptXml::getIntegerAttribute(tree,
+                                REQ_HARD_KEYBOARD_ATTR, 0);
+                        int32_t reqNavigation = AaptXml::getIntegerAttribute(tree,
+                                REQ_NAVIGATION_ATTR, 0);
+                        int32_t reqFiveWayNav = AaptXml::getIntegerAttribute(tree,
+                                REQ_FIVE_WAY_NAV_ATTR, 0);
                         printf("uses-configuration:");
                         if (reqTouchScreen != 0) {
                             printf(" reqTouchScreen='%d'", reqTouchScreen);
@@ -1353,26 +1226,26 @@
                     } else if (tag == "supports-input") {
                         withinSupportsInput = true;
                     } else if (tag == "supports-screens") {
-                        smallScreen = getIntegerAttribute(tree,
-                                SMALL_SCREEN_ATTR, NULL, 1);
-                        normalScreen = getIntegerAttribute(tree,
-                                NORMAL_SCREEN_ATTR, NULL, 1);
-                        largeScreen = getIntegerAttribute(tree,
-                                LARGE_SCREEN_ATTR, NULL, 1);
-                        xlargeScreen = getIntegerAttribute(tree,
-                                XLARGE_SCREEN_ATTR, NULL, 1);
-                        anyDensity = getIntegerAttribute(tree,
-                                ANY_DENSITY_ATTR, NULL, 1);
-                        requiresSmallestWidthDp = getIntegerAttribute(tree,
-                                REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
-                        compatibleWidthLimitDp = getIntegerAttribute(tree,
-                                COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
-                        largestWidthLimitDp = getIntegerAttribute(tree,
-                                LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
+                        smallScreen = AaptXml::getIntegerAttribute(tree,
+                                SMALL_SCREEN_ATTR, 1);
+                        normalScreen = AaptXml::getIntegerAttribute(tree,
+                                NORMAL_SCREEN_ATTR, 1);
+                        largeScreen = AaptXml::getIntegerAttribute(tree,
+                                LARGE_SCREEN_ATTR, 1);
+                        xlargeScreen = AaptXml::getIntegerAttribute(tree,
+                                XLARGE_SCREEN_ATTR, 1);
+                        anyDensity = AaptXml::getIntegerAttribute(tree,
+                                ANY_DENSITY_ATTR, 1);
+                        requiresSmallestWidthDp = AaptXml::getIntegerAttribute(tree,
+                                REQUIRES_SMALLEST_WIDTH_DP_ATTR, 0);
+                        compatibleWidthLimitDp = AaptXml::getIntegerAttribute(tree,
+                                COMPATIBLE_WIDTH_LIMIT_DP_ATTR, 0);
+                        largestWidthLimitDp = AaptXml::getIntegerAttribute(tree,
+                                LARGEST_WIDTH_LIMIT_DP_ATTR, 0);
                     } else if (tag == "feature-group") {
                         withinFeatureGroup = true;
                         FeatureGroup group;
-                        group.label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+                        group.label = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error);
                         if (error != "") {
                             fprintf(stderr, "ERROR getting 'android:label' attribute:"
                                     " %s\n", error.string());
@@ -1381,17 +1254,17 @@
                         featureGroups.add(group);
 
                     } else if (tag == "uses-feature") {
-                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
-                            int req = getIntegerAttribute(tree,
-                                    REQUIRED_ATTR, NULL, 1);
+                            int req = AaptXml::getIntegerAttribute(tree,
+                                    REQUIRED_ATTR, 1);
 
                             commonFeatures.features.add(name, req);
                             if (req) {
                                 addParentFeatures(&commonFeatures, name);
                             }
                         } else {
-                            int vers = getIntegerAttribute(tree,
+                            int vers = AaptXml::getIntegerAttribute(tree,
                                     GL_ES_VERSION_ATTR, &error);
                             if (error == "") {
                                 if (vers > commonFeatures.openGLESVersion) {
@@ -1400,7 +1273,7 @@
                             }
                         }
                     } else if (tag == "uses-permission") {
-                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
                             if (name == "android.permission.CAMERA") {
                                 addImpliedFeature(&impliedFeatures, "android.hardware.camera",
@@ -1478,15 +1351,15 @@
                             }
 
                             printUsesPermission(name,
-                                    getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
-                                    getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
+                                    AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
+                                    AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
                        } else {
                             fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
                                     error.string());
                             goto bail;
                         }
                     } else if (tag == "uses-package") {
-                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
                             printf("uses-package:'%s'\n",
                                     ResTable::normalizeForOutput(name.string()).string());
@@ -1496,7 +1369,7 @@
                                 goto bail;
                         }
                     } else if (tag == "original-package") {
-                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
                             printf("original-package:'%s'\n",
                                     ResTable::normalizeForOutput(name.string()).string());
@@ -1506,7 +1379,7 @@
                                 goto bail;
                         }
                     } else if (tag == "supports-gl-texture") {
-                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
                             printf("supports-gl-texture:'%s'\n",
                                     ResTable::normalizeForOutput(name.string()).string());
@@ -1524,9 +1397,9 @@
                         }
                         depth--;
                     } else if (tag == "package-verifier") {
-                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
-                            String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
+                            String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, &error);
                             if (publicKey != "" && error == "") {
                                 printf("package-verifier: name='%s' publicKey='%s'\n",
                                         ResTable::normalizeForOutput(name.string()).string(),
@@ -1553,35 +1426,38 @@
                     if (withinApplication) {
                         if(tag == "activity") {
                             withinActivity = true;
-                            activityName = getAttribute(tree, NAME_ATTR, &error);
+                            activityName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
                                         error.string());
                                 goto bail;
                             }
 
-                            activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+                            activityLabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR,
+                                    &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
                                         error.string());
                                 goto bail;
                             }
 
-                            activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+                            activityIcon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR,
+                                    &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
                                         error.string());
                                 goto bail;
                             }
 
-                            activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error);
+                            activityBanner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR,
+                                    &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
                                         error.string());
                                 goto bail;
                             }
 
-                            int32_t orien = getResolvedIntegerAttribute(&res, tree,
+                            int32_t orien = AaptXml::getResolvedIntegerAttribute(res, tree,
                                     SCREEN_ORIENTATION_ATTR, &error);
                             if (error == "") {
                                 if (orien == 0 || orien == 6 || orien == 8) {
@@ -1595,21 +1471,21 @@
                                 }
                             }
                         } else if (tag == "uses-library") {
-                            String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
+                            String8 libraryName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr,
                                         "ERROR getting 'android:name' attribute for uses-library"
                                         " %s\n", error.string());
                                 goto bail;
                             }
-                            int req = getIntegerAttribute(tree,
-                                    REQUIRED_ATTR, NULL, 1);
+                            int req = AaptXml::getIntegerAttribute(tree,
+                                    REQUIRED_ATTR, 1);
                             printf("uses-library%s:'%s'\n",
                                     req ? "" : "-not-required", ResTable::normalizeForOutput(
                                             libraryName.string()).string());
                         } else if (tag == "receiver") {
                             withinReceiver = true;
-                            receiverName = getAttribute(tree, NAME_ATTR, &error);
+                            receiverName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
 
                             if (error != "") {
                                 fprintf(stderr,
@@ -1618,7 +1494,8 @@
                                 goto bail;
                             }
 
-                            String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+                            String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR,
+                                    &error);
                             if (error == "") {
                                 if (permission == "android.permission.BIND_DEVICE_ADMIN") {
                                     hasBindDeviceAdminPermission = true;
@@ -1629,7 +1506,7 @@
                             }
                         } else if (tag == "service") {
                             withinService = true;
-                            serviceName = getAttribute(tree, NAME_ATTR, &error);
+                            serviceName = AaptXml::getAttribute(tree, NAME_ATTR, &error);
 
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:name' attribute for "
@@ -1637,7 +1514,8 @@
                                 goto bail;
                             }
 
-                            String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+                            String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR,
+                                    &error);
                             if (error == "") {
                                 if (permission == "android.permission.BIND_INPUT_METHOD") {
                                     hasBindInputMethodPermission = true;
@@ -1659,22 +1537,24 @@
                         } else if (tag == "provider") {
                             withinProvider = true;
 
-                            bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
+                            bool exported = AaptXml::getResolvedIntegerAttribute(res, tree,
+                                    EXPORTED_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
                                         " %s\n", error.string());
                                 goto bail;
                             }
 
-                            bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
-                                    GRANT_URI_PERMISSIONS_ATTR, &error);
+                            bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute(
+                                    res, tree, GRANT_URI_PERMISSIONS_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
                                         " %s\n", error.string());
                                 goto bail;
                             }
 
-                            String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
+                            String8 permission = AaptXml::getResolvedAttribute(res, tree,
+                                    PERMISSION_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
                                         " %s\n", error.string());
@@ -1685,7 +1565,8 @@
                                 permission == "android.permission.MANAGE_DOCUMENTS";
 
                         } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
-                            String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
+                            String8 metaDataName = AaptXml::getResolvedAttribute(res, tree,
+                                    NAME_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:name' attribute for "
                                         "meta-data:%s\n", error.string());
@@ -1693,12 +1574,12 @@
                             }
                             printf("meta-data: name='%s' ",
                                     ResTable::normalizeForOutput(metaDataName.string()).string());
-                            printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
+                            printResolvedResourceAttribute(res, tree, VALUE_ATTR, String8("value"),
                                     &error);
                             if (error != "") {
                                 // Try looking for a RESOURCE_ATTR
                                 error = "";
-                                printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
+                                printResolvedResourceAttribute(res, tree, RESOURCE_ATTR,
                                         String8("resource"), &error);
                                 if (error != "") {
                                     fprintf(stderr, "ERROR getting 'android:value' or "
@@ -1709,7 +1590,7 @@
                             }
                             printf("\n");
                         } else if (withinSupportsInput && tag == "input-type") {
-                            String8 name = getAttribute(tree, NAME_ATTR, &error);
+                            String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                             if (name != "" && error == "") {
                                 supportedInput.add(name);
                             } else {
@@ -1721,12 +1602,13 @@
                     } else if (withinFeatureGroup && tag == "uses-feature") {
                         FeatureGroup& top = featureGroups.editTop();
 
-                        String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
                             top.features.add(name, true);
                             addParentFeatures(&top, name);
                         } else {
-                            int vers = getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error);
+                            int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR,
+                                    &error);
                             if (error == "") {
                                 if (vers > top.openGLESVersion) {
                                     top.openGLESVersion = vers;
@@ -1754,7 +1636,7 @@
                         actCameraSecure = false;
                         catLauncher = false;
                     } else if (withinService && tag == "meta-data") {
-                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (error != "") {
                             fprintf(stderr, "ERROR getting 'android:name' attribute for"
                                     " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
@@ -1768,7 +1650,8 @@
                                 offHost = false;
                             }
 
-                            String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
+                            String8 xmlPath = AaptXml::getResolvedAttribute(res, tree,
+                                    RESOURCE_ATTR, &error);
                             if (error != "") {
                                 fprintf(stderr, "ERROR getting 'android:resource' attribute for"
                                         " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
@@ -1797,7 +1680,7 @@
                 } else if ((depth == 5) && withinIntentFilter) {
                     String8 action;
                     if (tag == "action") {
-                        action = getAttribute(tree, NAME_ATTR, &error);
+                        action = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (error != "") {
                             fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
                                     error.string());
@@ -1849,7 +1732,7 @@
                     }
 
                     if (tag == "category") {
-                        String8 category = getAttribute(tree, NAME_ATTR, &error);
+                        String8 category = AaptXml::getAttribute(tree, NAME_ATTR, &error);
                         if (error != "") {
                             fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
                                     error.string());
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
index dd40b20..f24a023b 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -60,9 +60,6 @@
 
 int dumpResources(Bundle* bundle);
 
-String8 getAttribute(const ResXMLTree& tree, const char* ns,
-                            const char* attr, String8* outError);
-
 status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets,
                                 FILE* fp, bool includeRaw);
 #endif // __MAIN_H
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 7979a1d..5deeca2 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -4,6 +4,7 @@
 // Build resource files from raw assets.
 //
 #include "AaptAssets.h"
+#include "AaptXml.h"
 #include "CacheUpdater.h"
 #include "CrunchCache.h"
 #include "FileFinder.h"
@@ -805,6 +806,20 @@
         }
     }
 
+    if (bundle->getPlatformBuildVersionCode() != "") {
+        if (!addTagAttribute(root, "", "platformBuildVersionCode",
+                    bundle->getPlatformBuildVersionCode(), errorOnFailedInsert, true)) {
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    if (bundle->getPlatformBuildVersionName() != "") {
+        if (!addTagAttribute(root, "", "platformBuildVersionName",
+                    bundle->getPlatformBuildVersionName(), errorOnFailedInsert, true)) {
+            return UNKNOWN_ERROR;
+        }
+    }
+
     if (bundle->getDebugMode()) {
         sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
         if (application != NULL) {
@@ -881,6 +896,106 @@
     return NO_ERROR;
 }
 
+static int32_t getPlatformAssetCookie(const AssetManager& assets) {
+    // Find the system package (0x01). AAPT always generates attributes
+    // with the type 0x01, so we're looking for the first attribute
+    // resource in the system package.
+    const ResTable& table = assets.getResources(true);
+    Res_value val;
+    ssize_t idx = table.getResource(0x01010000, &val, true);
+    if (idx != NO_ERROR) {
+        // Try as a bag.
+        const ResTable::bag_entry* entry;
+        ssize_t cnt = table.lockBag(0x01010000, &entry);
+        if (cnt >= 0) {
+            idx = entry->stringBlock;
+        }
+        table.unlockBag(entry);
+    }
+
+    if (idx < 0) {
+        return 0;
+    }
+    return table.getTableCookie(idx);
+}
+
+enum {
+    VERSION_CODE_ATTR = 0x0101021b,
+    VERSION_NAME_ATTR = 0x0101021c,
+};
+
+static ssize_t extractPlatformBuildVersion(ResXMLTree& tree, Bundle* bundle) {
+    size_t len;
+    ResXMLTree::event_code_t code;
+    while ((code = tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code != ResXMLTree::START_TAG) {
+            continue;
+        }
+
+        const char16_t* ctag16 = tree.getElementName(&len);
+        if (ctag16 == NULL) {
+            fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n");
+            return UNKNOWN_ERROR;
+        }
+
+        String8 tag(ctag16, len);
+        if (tag != "manifest") {
+            continue;
+        }
+
+        String8 error;
+        int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
+        if (error != "") {
+            fprintf(stderr, "ERROR: failed to get platform version code\n");
+            return UNKNOWN_ERROR;
+        }
+
+        if (versionCode >= 0 && bundle->getPlatformBuildVersionCode() == "") {
+            bundle->setPlatformBuildVersionCode(String8::format("%d", versionCode));
+        }
+
+        String8 versionName = AaptXml::getAttribute(tree, VERSION_NAME_ATTR, &error);
+        if (error != "") {
+            fprintf(stderr, "ERROR: failed to get platform version name\n");
+            return UNKNOWN_ERROR;
+        }
+
+        if (versionName != "" && bundle->getPlatformBuildVersionName() == "") {
+            bundle->setPlatformBuildVersionName(versionName);
+        }
+        return NO_ERROR;
+    }
+
+    fprintf(stderr, "ERROR: no <manifest> tag found in platform AndroidManifest.xml\n");
+    return UNKNOWN_ERROR;
+}
+
+static ssize_t extractPlatformBuildVersion(AssetManager& assets, Bundle* bundle) {
+    int32_t cookie = getPlatformAssetCookie(assets);
+    if (cookie == 0) {
+        fprintf(stderr, "ERROR: Platform package not found\n");
+        return UNKNOWN_ERROR;
+    }
+
+    ResXMLTree tree;
+    Asset* asset = assets.openNonAsset(cookie, "AndroidManifest.xml", Asset::ACCESS_STREAMING);
+    if (asset == NULL) {
+        fprintf(stderr, "ERROR: Platform AndroidManifest.xml not found\n");
+        return UNKNOWN_ERROR;
+    }
+
+    ssize_t result = NO_ERROR;
+    if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) {
+        fprintf(stderr, "ERROR: Platform AndroidManifest.xml is corrupt\n");
+        result = UNKNOWN_ERROR;
+    } else {
+        result = extractPlatformBuildVersion(tree, bundle);
+    }
+
+    delete asset;
+    return result;
+}
+
 #define ASSIGN_IT(n) \
         do { \
             ssize_t index = resources->indexOfKey(String8(#n)); \
@@ -1356,6 +1471,17 @@
         return UNKNOWN_ERROR;
     }
 
+    // If we're not overriding the platform build versions,
+    // extract them from the platform APK.
+    if (packageType != ResourceTable::System &&
+            (bundle->getPlatformBuildVersionCode() == "" ||
+            bundle->getPlatformBuildVersionName() == "")) {
+        err = extractPlatformBuildVersion(assets->getAssetManager(), bundle);
+        if (err != NO_ERROR) {
+            return UNKNOWN_ERROR;
+        }
+    }
+
     const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
     String8 manifestPath(manifestFile->getPrintableSource());
 
@@ -2636,13 +2762,14 @@
                 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
                 return -1;
             }
-            pkg = getAttribute(tree, NULL, "package", NULL);
+            pkg = AaptXml::getAttribute(tree, NULL, "package");
         } else if (depth == 2) {
             if (tag == "application") {
                 inApplication = true;
                 keepTag = true;
 
-                String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+                String8 agent = AaptXml::getAttribute(tree,
+                        "http://schemas.android.com/apk/res/android",
                         "backupAgent", &error);
                 if (agent.length() > 0) {
                     addProguardKeepRule(keep, agent, pkg.string(),
@@ -2658,8 +2785,8 @@
             }
         }
         if (keepTag) {
-            String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
-                    "name", &error);
+            String8 name = AaptXml::getAttribute(tree,
+                    "http://schemas.android.com/apk/res/android", "name", &error);
             if (error != "") {
                 fprintf(stderr, "ERROR: %s\n", error.string());
                 return -1;