Merge "Remove dead ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN"
diff --git a/Android.mk b/Android.mk
index 9029f4e..5713f3e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -686,7 +686,7 @@
 # Common sources for doc check and api check
 common_src_files := \
 	$(call find-other-html-files, $(html_dirs)) \
-	$(addprefix ../../libcore/, $(libcore_to_document)) \
+	$(addprefix ../../, $(libcore_to_document)) \
 	$(addprefix ../../external/junit/, $(junit_to_document))
 
 # These are relative to frameworks/base
diff --git a/api/current.txt b/api/current.txt
index 9fe6857..f75ef0b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -241,10 +241,8 @@
     field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd
     field public static final int activityCloseEnterAnimation = 16842938; // 0x10100ba
     field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb
-    field public static final int activityHeight = 16844021; // 0x10104f5
     field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8
     field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9
-    field public static final int activityWidth = 16844020; // 0x10104f4
     field public static final int addPrintersActivity = 16843750; // 0x10103e6
     field public static final int addStatesFromChildren = 16842992; // 0x10100f0
     field public static final int adjustViewBounds = 16843038; // 0x101011e
@@ -323,7 +321,7 @@
     field public static final int buttonBarNeutralButtonStyle = 16843914; // 0x101048a
     field public static final int buttonBarPositiveButtonStyle = 16843913; // 0x1010489
     field public static final int buttonBarStyle = 16843566; // 0x101032e
-    field public static final int buttonGravity = 16844029; // 0x10104fd
+    field public static final int buttonGravity = 16844030; // 0x10104fe
     field public static final int buttonStyle = 16842824; // 0x1010048
     field public static final int buttonStyleInset = 16842826; // 0x101004a
     field public static final int buttonStyleSmall = 16842825; // 0x1010049
@@ -373,7 +371,7 @@
     field public static final int codes = 16843330; // 0x1010242
     field public static final int collapseColumns = 16843083; // 0x101014b
     field public static final int collapseContentDescription = 16843984; // 0x10104d0
-    field public static final int collapseIcon = 16844030; // 0x10104fe
+    field public static final int collapseIcon = 16844031; // 0x10104ff
     field public static final int color = 16843173; // 0x10101a5
     field public static final int colorAccent = 16843829; // 0x1010435
     field public static final int colorActivatedHighlight = 16843664; // 0x1010390
@@ -414,7 +412,7 @@
     field public static final int contentInsetRight = 16843862; // 0x1010456
     field public static final int contentInsetStart = 16843859; // 0x1010453
     field public static final int contextClickable = 16844007; // 0x10104e7
-    field public static final int contextPopupMenuStyle = 16844032; // 0x1010500
+    field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
     field public static final int controlX1 = 16843772; // 0x10103fc
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
@@ -673,8 +671,10 @@
     field public static final int indicatorStart = 16843729; // 0x10103d1
     field public static final int inflatedId = 16842995; // 0x10100f3
     field public static final int initOrder = 16842778; // 0x101001a
+    field public static final int initialHeight = 16844021; // 0x10104f5
     field public static final int initialKeyguardLayout = 16843714; // 0x10103c2
     field public static final int initialLayout = 16843345; // 0x1010251
+    field public static final int initialWidth = 16844020; // 0x10104f4
     field public static final int innerRadius = 16843359; // 0x101025f
     field public static final int innerRadiusRatio = 16843163; // 0x101019b
     field public static final deprecated int inputMethod = 16843112; // 0x1010168
@@ -780,7 +780,7 @@
     field public static final int layout_y = 16843136; // 0x1010180
     field public static final int left = 16843181; // 0x10101ad
     field public static final int letterSpacing = 16843958; // 0x10104b6
-    field public static final int level = 16844031; // 0x10104ff
+    field public static final int level = 16844032; // 0x1010500
     field public static final int lineSpacingExtra = 16843287; // 0x1010217
     field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
     field public static final int lines = 16843092; // 0x1010154
@@ -813,7 +813,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
-    field public static final int maxButtonHeight = 16844028; // 0x10104fc
+    field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
     field public static final int maxHeight = 16843040; // 0x1010120
@@ -841,6 +841,7 @@
     field public static final int minResizeWidth = 16843669; // 0x1010395
     field public static final int minSdkVersion = 16843276; // 0x101020c
     field public static final int minWidth = 16843071; // 0x101013f
+    field public static final int minimalSize = 16844022; // 0x10104f6
     field public static final int minimumHorizontalAngle = 16843901; // 0x101047d
     field public static final int minimumVerticalAngle = 16843902; // 0x101047e
     field public static final int mipMap = 16843725; // 0x10103cd
@@ -997,7 +998,7 @@
     field public static final int resizeClip = 16843983; // 0x10104cf
     field public static final int resizeMode = 16843619; // 0x1010363
     field public static final int resizeable = 16843405; // 0x101028d
-    field public static final int resizeableActivity = 16844022; // 0x10104f6
+    field public static final int resizeableActivity = 16844023; // 0x10104f7
     field public static final int resource = 16842789; // 0x1010025
     field public static final int restoreAnyVersion = 16843450; // 0x10102ba
     field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d
@@ -1221,7 +1222,7 @@
     field public static final int textAppearanceListItemSmall = 16843679; // 0x101039f
     field public static final int textAppearanceMedium = 16842817; // 0x1010041
     field public static final int textAppearanceMediumInverse = 16842820; // 0x1010044
-    field public static final int textAppearancePopupMenuHeader = 16844033; // 0x1010501
+    field public static final int textAppearancePopupMenuHeader = 16844034; // 0x1010502
     field public static final int textAppearanceSearchResultSubtitle = 16843424; // 0x10102a0
     field public static final int textAppearanceSearchResultTitle = 16843425; // 0x10102a1
     field public static final int textAppearanceSmall = 16842818; // 0x1010042
@@ -1290,11 +1291,11 @@
     field public static final int tintMode = 16843771; // 0x10103fb
     field public static final int title = 16843233; // 0x10101e1
     field public static final int titleCondensed = 16843234; // 0x10101e2
-    field public static final int titleMargin = 16844023; // 0x10104f7
-    field public static final int titleMarginBottom = 16844027; // 0x10104fb
-    field public static final int titleMarginEnd = 16844025; // 0x10104f9
-    field public static final int titleMarginStart = 16844024; // 0x10104f8
-    field public static final int titleMarginTop = 16844026; // 0x10104fa
+    field public static final int titleMargin = 16844024; // 0x10104f8
+    field public static final int titleMarginBottom = 16844028; // 0x10104fc
+    field public static final int titleMarginEnd = 16844026; // 0x10104fa
+    field public static final int titleMarginStart = 16844025; // 0x10104f9
+    field public static final int titleMarginTop = 16844027; // 0x10104fb
     field public static final int titleTextAppearance = 16843822; // 0x101042e
     field public static final int titleTextColor = 16844003; // 0x10104e3
     field public static final int titleTextStyle = 16843512; // 0x10102f8
@@ -1394,6 +1395,7 @@
     field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
     field public static final int windowAnimationStyle = 16842926; // 0x10100ae
     field public static final int windowBackground = 16842836; // 0x1010054
+    field public static final int windowBackgroundFallback = 16844035; // 0x1010503
     field public static final int windowClipToOutline = 16843947; // 0x10104ab
     field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
     field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -4758,6 +4760,7 @@
     method public android.graphics.drawable.Icon getLargeIcon();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
+    method public android.app.Notification.Topic[] getTopics();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
     field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -4922,6 +4925,7 @@
     method public android.app.Notification.Builder addAction(android.app.Notification.Action);
     method public android.app.Notification.Builder addExtras(android.os.Bundle);
     method public android.app.Notification.Builder addPerson(java.lang.String);
+    method public android.app.Notification.Builder addTopic(android.app.Notification.Topic);
     method public android.app.Notification build();
     method public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
@@ -5029,6 +5033,16 @@
     field protected android.app.Notification.Builder mBuilder;
   }
 
+  public static class Notification.Topic implements android.os.Parcelable {
+    ctor public Notification.Topic(java.lang.String, java.lang.CharSequence);
+    method public android.app.Notification.Topic clone();
+    method public int describeContents();
+    method public java.lang.String getId();
+    method public java.lang.CharSequence getLabel();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.Notification.Topic> CREATOR;
+  }
+
   public static final class Notification.WearableExtender implements android.app.Notification.Extender {
     ctor public Notification.WearableExtender();
     ctor public Notification.WearableExtender(android.app.Notification);
@@ -8924,8 +8938,8 @@
     field public int configChanges;
     field public int documentLaunchMode;
     field public int flags;
-    field public android.content.pm.ActivityInfo.InitialLayout initialLayout;
     field public int launchMode;
+    field public android.content.pm.ActivityInfo.Layout layout;
     field public int maxRecents;
     field public java.lang.String parentActivityName;
     field public java.lang.String permission;
@@ -8938,11 +8952,12 @@
     field public int uiOptions;
   }
 
-  public static final class ActivityInfo.InitialLayout {
-    ctor public ActivityInfo.InitialLayout(int, float, int, float, int);
+  public static final class ActivityInfo.Layout {
+    ctor public ActivityInfo.Layout(int, float, int, float, int, int);
     field public final int gravity;
     field public final int height;
     field public final float heightFraction;
+    field public final int minimalSize;
     field public final int width;
     field public final float widthFraction;
   }
@@ -25020,6 +25035,7 @@
     field public static final int OUTGOING_TYPE = 2; // 0x2
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+    field public static final java.lang.String POST_DIAL_DIGITS = "post_dial_digits";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
     field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
     field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
@@ -30517,6 +30533,8 @@
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
     field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
+    field public static final java.lang.String EXTRA_CALL_SUBJECT_CHARACTER_ENCODING = "android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING";
+    field public static final java.lang.String EXTRA_CALL_SUBJECT_MAX_LENGTH = "android.telecom.extra.CALL_SUBJECT_MAX_LENGTH";
     field public static final int NO_HIGHLIGHT_COLOR = 0; // 0x0
     field public static final int NO_RESOURCE_ID = -1; // 0xffffffff
     field public static final java.lang.String SCHEME_SIP = "sip";
@@ -35315,8 +35333,10 @@
     field public static final int KEYCODE_CLEAR = 28; // 0x1c
     field public static final int KEYCODE_COMMA = 55; // 0x37
     field public static final int KEYCODE_CONTACTS = 207; // 0xcf
+    field public static final int KEYCODE_COPY = 278; // 0x116
     field public static final int KEYCODE_CTRL_LEFT = 113; // 0x71
     field public static final int KEYCODE_CTRL_RIGHT = 114; // 0x72
+    field public static final int KEYCODE_CUT = 277; // 0x115
     field public static final int KEYCODE_D = 32; // 0x20
     field public static final int KEYCODE_DEL = 67; // 0x43
     field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
@@ -35434,6 +35454,7 @@
     field public static final int KEYCODE_PAGE_DOWN = 93; // 0x5d
     field public static final int KEYCODE_PAGE_UP = 92; // 0x5c
     field public static final int KEYCODE_PAIRING = 225; // 0xe1
+    field public static final int KEYCODE_PASTE = 279; // 0x117
     field public static final int KEYCODE_PERIOD = 56; // 0x38
     field public static final int KEYCODE_PICTSYMBOLS = 94; // 0x5e
     field public static final int KEYCODE_PLUS = 81; // 0x51
@@ -39214,6 +39235,7 @@
     method public abstract java.lang.String getDefaultTextEncodingName();
     method public static java.lang.String getDefaultUserAgent(android.content.Context);
     method public abstract deprecated android.webkit.WebSettings.ZoomDensity getDefaultZoom();
+    method public abstract int getDisabledActionModeMenuItems();
     method public abstract boolean getDisplayZoomControls();
     method public abstract boolean getDomStorageEnabled();
     method public abstract java.lang.String getFantasyFontFamily();
@@ -39257,6 +39279,7 @@
     method public abstract void setDefaultFontSize(int);
     method public abstract void setDefaultTextEncodingName(java.lang.String);
     method public abstract deprecated void setDefaultZoom(android.webkit.WebSettings.ZoomDensity);
+    method public abstract void setDisabledActionModeMenuItems(int);
     method public abstract void setDisplayZoomControls(boolean);
     method public abstract void setDomStorageEnabled(boolean);
     method public abstract deprecated void setEnableSmoothTransition(boolean);
@@ -39296,6 +39319,10 @@
     field public static final int LOAD_DEFAULT = -1; // 0xffffffff
     field public static final deprecated int LOAD_NORMAL = 0; // 0x0
     field public static final int LOAD_NO_CACHE = 2; // 0x2
+    field public static final int MENU_ITEM_NONE = 0; // 0x0
+    field public static final int MENU_ITEM_PROCESS_TEXT = 4; // 0x4
+    field public static final int MENU_ITEM_SHARE = 1; // 0x1
+    field public static final int MENU_ITEM_WEB_SEARCH = 2; // 0x2
     field public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0; // 0x0
     field public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2; // 0x2
     field public static final int MIXED_CONTENT_NEVER_ALLOW = 1; // 0x1
diff --git a/api/system-current.txt b/api/system-current.txt
index 5e1aa41..c4c6f1c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -333,10 +333,8 @@
     field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd
     field public static final int activityCloseEnterAnimation = 16842938; // 0x10100ba
     field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb
-    field public static final int activityHeight = 16844021; // 0x10104f5
     field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8
     field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9
-    field public static final int activityWidth = 16844020; // 0x10104f4
     field public static final int addPrintersActivity = 16843750; // 0x10103e6
     field public static final int addStatesFromChildren = 16842992; // 0x10100f0
     field public static final int adjustViewBounds = 16843038; // 0x101011e
@@ -415,7 +413,7 @@
     field public static final int buttonBarNeutralButtonStyle = 16843914; // 0x101048a
     field public static final int buttonBarPositiveButtonStyle = 16843913; // 0x1010489
     field public static final int buttonBarStyle = 16843566; // 0x101032e
-    field public static final int buttonGravity = 16844029; // 0x10104fd
+    field public static final int buttonGravity = 16844030; // 0x10104fe
     field public static final int buttonStyle = 16842824; // 0x1010048
     field public static final int buttonStyleInset = 16842826; // 0x101004a
     field public static final int buttonStyleSmall = 16842825; // 0x1010049
@@ -465,7 +463,7 @@
     field public static final int codes = 16843330; // 0x1010242
     field public static final int collapseColumns = 16843083; // 0x101014b
     field public static final int collapseContentDescription = 16843984; // 0x10104d0
-    field public static final int collapseIcon = 16844030; // 0x10104fe
+    field public static final int collapseIcon = 16844031; // 0x10104ff
     field public static final int color = 16843173; // 0x10101a5
     field public static final int colorAccent = 16843829; // 0x1010435
     field public static final int colorActivatedHighlight = 16843664; // 0x1010390
@@ -506,7 +504,7 @@
     field public static final int contentInsetRight = 16843862; // 0x1010456
     field public static final int contentInsetStart = 16843859; // 0x1010453
     field public static final int contextClickable = 16844007; // 0x10104e7
-    field public static final int contextPopupMenuStyle = 16844032; // 0x1010500
+    field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
     field public static final int controlX1 = 16843772; // 0x10103fc
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
@@ -765,8 +763,10 @@
     field public static final int indicatorStart = 16843729; // 0x10103d1
     field public static final int inflatedId = 16842995; // 0x10100f3
     field public static final int initOrder = 16842778; // 0x101001a
+    field public static final int initialHeight = 16844021; // 0x10104f5
     field public static final int initialKeyguardLayout = 16843714; // 0x10103c2
     field public static final int initialLayout = 16843345; // 0x1010251
+    field public static final int initialWidth = 16844020; // 0x10104f4
     field public static final int innerRadius = 16843359; // 0x101025f
     field public static final int innerRadiusRatio = 16843163; // 0x101019b
     field public static final deprecated int inputMethod = 16843112; // 0x1010168
@@ -872,7 +872,7 @@
     field public static final int layout_y = 16843136; // 0x1010180
     field public static final int left = 16843181; // 0x10101ad
     field public static final int letterSpacing = 16843958; // 0x10104b6
-    field public static final int level = 16844031; // 0x10104ff
+    field public static final int level = 16844032; // 0x1010500
     field public static final int lineSpacingExtra = 16843287; // 0x1010217
     field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
     field public static final int lines = 16843092; // 0x1010154
@@ -905,7 +905,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
-    field public static final int maxButtonHeight = 16844028; // 0x10104fc
+    field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
     field public static final int maxHeight = 16843040; // 0x1010120
@@ -933,6 +933,7 @@
     field public static final int minResizeWidth = 16843669; // 0x1010395
     field public static final int minSdkVersion = 16843276; // 0x101020c
     field public static final int minWidth = 16843071; // 0x101013f
+    field public static final int minimalSize = 16844022; // 0x10104f6
     field public static final int minimumHorizontalAngle = 16843901; // 0x101047d
     field public static final int minimumVerticalAngle = 16843902; // 0x101047e
     field public static final int mipMap = 16843725; // 0x10103cd
@@ -1089,7 +1090,7 @@
     field public static final int resizeClip = 16843983; // 0x10104cf
     field public static final int resizeMode = 16843619; // 0x1010363
     field public static final int resizeable = 16843405; // 0x101028d
-    field public static final int resizeableActivity = 16844022; // 0x10104f6
+    field public static final int resizeableActivity = 16844023; // 0x10104f7
     field public static final int resource = 16842789; // 0x1010025
     field public static final int restoreAnyVersion = 16843450; // 0x10102ba
     field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d
@@ -1317,7 +1318,7 @@
     field public static final int textAppearanceListItemSmall = 16843679; // 0x101039f
     field public static final int textAppearanceMedium = 16842817; // 0x1010041
     field public static final int textAppearanceMediumInverse = 16842820; // 0x1010044
-    field public static final int textAppearancePopupMenuHeader = 16844033; // 0x1010501
+    field public static final int textAppearancePopupMenuHeader = 16844034; // 0x1010502
     field public static final int textAppearanceSearchResultSubtitle = 16843424; // 0x10102a0
     field public static final int textAppearanceSearchResultTitle = 16843425; // 0x10102a1
     field public static final int textAppearanceSmall = 16842818; // 0x1010042
@@ -1386,11 +1387,11 @@
     field public static final int tintMode = 16843771; // 0x10103fb
     field public static final int title = 16843233; // 0x10101e1
     field public static final int titleCondensed = 16843234; // 0x10101e2
-    field public static final int titleMargin = 16844023; // 0x10104f7
-    field public static final int titleMarginBottom = 16844027; // 0x10104fb
-    field public static final int titleMarginEnd = 16844025; // 0x10104f9
-    field public static final int titleMarginStart = 16844024; // 0x10104f8
-    field public static final int titleMarginTop = 16844026; // 0x10104fa
+    field public static final int titleMargin = 16844024; // 0x10104f8
+    field public static final int titleMarginBottom = 16844028; // 0x10104fc
+    field public static final int titleMarginEnd = 16844026; // 0x10104fa
+    field public static final int titleMarginStart = 16844025; // 0x10104f9
+    field public static final int titleMarginTop = 16844027; // 0x10104fb
     field public static final int titleTextAppearance = 16843822; // 0x101042e
     field public static final int titleTextColor = 16844003; // 0x10104e3
     field public static final int titleTextStyle = 16843512; // 0x10102f8
@@ -1490,6 +1491,7 @@
     field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
     field public static final int windowAnimationStyle = 16842926; // 0x10100ae
     field public static final int windowBackground = 16842836; // 0x1010054
+    field public static final int windowBackgroundFallback = 16844035; // 0x1010503
     field public static final int windowClipToOutline = 16843947; // 0x10104ab
     field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
     field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -4875,6 +4877,7 @@
     method public android.graphics.drawable.Icon getLargeIcon();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
+    method public android.app.Notification.Topic[] getTopics();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
     field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5039,6 +5042,7 @@
     method public android.app.Notification.Builder addAction(android.app.Notification.Action);
     method public android.app.Notification.Builder addExtras(android.os.Bundle);
     method public android.app.Notification.Builder addPerson(java.lang.String);
+    method public android.app.Notification.Builder addTopic(android.app.Notification.Topic);
     method public android.app.Notification build();
     method public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
@@ -5146,6 +5150,16 @@
     field protected android.app.Notification.Builder mBuilder;
   }
 
+  public static class Notification.Topic implements android.os.Parcelable {
+    ctor public Notification.Topic(java.lang.String, java.lang.CharSequence);
+    method public android.app.Notification.Topic clone();
+    method public int describeContents();
+    method public java.lang.String getId();
+    method public java.lang.CharSequence getLabel();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.Notification.Topic> CREATOR;
+  }
+
   public static final class Notification.WearableExtender implements android.app.Notification.Extender {
     ctor public Notification.WearableExtender();
     ctor public Notification.WearableExtender(android.app.Notification);
@@ -9181,8 +9195,8 @@
     field public int configChanges;
     field public int documentLaunchMode;
     field public int flags;
-    field public android.content.pm.ActivityInfo.InitialLayout initialLayout;
     field public int launchMode;
+    field public android.content.pm.ActivityInfo.Layout layout;
     field public int maxRecents;
     field public java.lang.String parentActivityName;
     field public java.lang.String permission;
@@ -9195,11 +9209,12 @@
     field public int uiOptions;
   }
 
-  public static final class ActivityInfo.InitialLayout {
-    ctor public ActivityInfo.InitialLayout(int, float, int, float, int);
+  public static final class ActivityInfo.Layout {
+    ctor public ActivityInfo.Layout(int, float, int, float, int, int);
     field public final int gravity;
     field public final int height;
     field public final float heightFraction;
+    field public final int minimalSize;
     field public final int width;
     field public final float widthFraction;
   }
@@ -9521,6 +9536,7 @@
     method public void setAppLabel(java.lang.CharSequence);
     method public void setAppPackageName(java.lang.String);
     method public void setGrantedRuntimePermissions(java.lang.String[]);
+    method public void setInstallFlagsQuick();
     method public void setInstallLocation(int);
     method public void setOriginatingUid(int);
     method public void setOriginatingUri(android.net.Uri);
@@ -25741,7 +25757,8 @@
     ctor public UserHandle(android.os.Parcel);
     method public int describeContents();
     method public int getIdentifier();
-    method public boolean isOwner();
+    method public deprecated boolean isOwner();
+    method public boolean isSystem();
     method public static int myUserId();
     method public static android.os.UserHandle readFromParcel(android.os.Parcel);
     method public void writeToParcel(android.os.Parcel, int);
@@ -26976,6 +26993,7 @@
     field public static final int OUTGOING_TYPE = 2; // 0x2
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+    field public static final java.lang.String POST_DIAL_DIGITS = "post_dial_digits";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
     field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
     field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
@@ -32717,6 +32735,8 @@
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
     field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
+    field public static final java.lang.String EXTRA_CALL_SUBJECT_CHARACTER_ENCODING = "android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING";
+    field public static final java.lang.String EXTRA_CALL_SUBJECT_MAX_LENGTH = "android.telecom.extra.CALL_SUBJECT_MAX_LENGTH";
     field public static final int NO_HIGHLIGHT_COLOR = 0; // 0x0
     field public static final int NO_RESOURCE_ID = -1; // 0xffffffff
     field public static final java.lang.String SCHEME_SIP = "sip";
@@ -37610,8 +37630,10 @@
     field public static final int KEYCODE_CLEAR = 28; // 0x1c
     field public static final int KEYCODE_COMMA = 55; // 0x37
     field public static final int KEYCODE_CONTACTS = 207; // 0xcf
+    field public static final int KEYCODE_COPY = 278; // 0x116
     field public static final int KEYCODE_CTRL_LEFT = 113; // 0x71
     field public static final int KEYCODE_CTRL_RIGHT = 114; // 0x72
+    field public static final int KEYCODE_CUT = 277; // 0x115
     field public static final int KEYCODE_D = 32; // 0x20
     field public static final int KEYCODE_DEL = 67; // 0x43
     field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
@@ -37729,6 +37751,7 @@
     field public static final int KEYCODE_PAGE_DOWN = 93; // 0x5d
     field public static final int KEYCODE_PAGE_UP = 92; // 0x5c
     field public static final int KEYCODE_PAIRING = 225; // 0xe1
+    field public static final int KEYCODE_PASTE = 279; // 0x117
     field public static final int KEYCODE_PERIOD = 56; // 0x38
     field public static final int KEYCODE_PICTSYMBOLS = 94; // 0x5e
     field public static final int KEYCODE_PLUS = 81; // 0x51
@@ -41574,6 +41597,7 @@
     method public abstract java.lang.String getDefaultTextEncodingName();
     method public static java.lang.String getDefaultUserAgent(android.content.Context);
     method public abstract deprecated android.webkit.WebSettings.ZoomDensity getDefaultZoom();
+    method public abstract int getDisabledActionModeMenuItems();
     method public abstract boolean getDisplayZoomControls();
     method public abstract boolean getDomStorageEnabled();
     method public abstract java.lang.String getFantasyFontFamily();
@@ -41623,6 +41647,7 @@
     method public abstract void setDefaultFontSize(int);
     method public abstract void setDefaultTextEncodingName(java.lang.String);
     method public abstract deprecated void setDefaultZoom(android.webkit.WebSettings.ZoomDensity);
+    method public abstract void setDisabledActionModeMenuItems(int);
     method public abstract void setDisplayZoomControls(boolean);
     method public abstract void setDomStorageEnabled(boolean);
     method public abstract deprecated void setEnableSmoothTransition(boolean);
@@ -41667,6 +41692,10 @@
     field public static final int LOAD_DEFAULT = -1; // 0xffffffff
     field public static final deprecated int LOAD_NORMAL = 0; // 0x0
     field public static final int LOAD_NO_CACHE = 2; // 0x2
+    field public static final int MENU_ITEM_NONE = 0; // 0x0
+    field public static final int MENU_ITEM_PROCESS_TEXT = 4; // 0x4
+    field public static final int MENU_ITEM_SHARE = 1; // 0x1
+    field public static final int MENU_ITEM_WEB_SEARCH = 2; // 0x2
     field public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0; // 0x0
     field public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2; // 0x2
     field public static final int MIXED_CONTENT_NEVER_ALLOW = 1; // 0x1
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 81da6af..1e2e33d 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -120,7 +120,7 @@
         out.println(
                 "usage: am [subcommand] [options]\n" +
                 "usage: am start [-D] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" +
-                "               [--sampling INTERVAL] [-R COUNT] [-S] [--opengl-trace]\n" +
+                "               [--sampling INTERVAL] [-R COUNT] [-S]\n" +
                 "               [--track-allocation] [--user <USER_ID> | current] <INTENT>\n" +
                 "       am startservice [--user <USER_ID> | current] <INTENT>\n" +
                 "       am stopservice [--user <USER_ID> | current] <INTENT>\n" +
@@ -182,7 +182,6 @@
                 "    -R: repeat the activity launch <COUNT> times.  Prior to each repeat,\n" +
                 "        the top activity will be finished.\n" +
                 "    -S: force stop the target app before starting the activity\n" +
-                "    --opengl-trace: enable tracing of OpenGL functions\n" +
                 "    --track-allocation: enable tracking of object allocations\n" +
                 "    --user <USER_ID> | current: Specify which user to run as; if not\n" +
                 "        specified then run as the current user.\n" +
@@ -739,8 +738,6 @@
                 mRepeat = Integer.parseInt(nextArgRequired());
             } else if (opt.equals("-S")) {
                 mStopOption = true;
-            } else if (opt.equals("--opengl-trace")) {
-                mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
             } else if (opt.equals("--track-allocation")) {
                 mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
             } else if (opt.equals("--user")) {
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
index 9d60ee1..6b2f460 100644
--- a/cmds/idmap/create.cpp
+++ b/cmds/idmap/create.cpp
@@ -57,7 +57,7 @@
 
     int write_idmap(int fd, const uint32_t *data, size_t size)
     {
-        if (lseek(fd, SEEK_SET, 0) < 0) {
+        if (lseek(fd, 0, SEEK_SET) < 0) {
             return -1;
         }
         size_t bytesLeft = size;
@@ -86,7 +86,7 @@
 
         char buf[N];
         size_t bytesLeft = N;
-        if (lseek(idmap_fd, SEEK_SET, 0) < 0) {
+        if (lseek(idmap_fd, 0, SEEK_SET) < 0) {
             return true;
         }
         for (;;) {
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 393956f..3f8e311 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -283,7 +283,7 @@
             } else if (args.length == 2) {
                 if (args[0].equalsIgnoreCase("-p")) {
                     validCommand = true;
-                    return displayPackageFilePath(args[1]);
+                    return displayPackageFilePath(args[1], UserHandle.USER_OWNER);
                 }
             }
             return 1;
@@ -767,12 +767,25 @@
     }
 
     private int runPath() {
+        int userId = UserHandle.USER_OWNER;
+        String option = nextOption();
+        if (option != null && option.equals("--user")) {
+            String optionData = nextOptionData();
+            if (optionData == null || !isNumber(optionData)) {
+                System.err.println("Error: no USER_ID specified");
+                showUsage();
+                return 1;
+            } else {
+                userId = Integer.parseInt(optionData);
+            }
+        }
+
         String pkg = nextArg();
         if (pkg == null) {
             System.err.println("Error: no package specified");
             return 1;
         }
-        return displayPackageFilePath(pkg);
+        return displayPackageFilePath(pkg, userId);
     }
 
     private int runDump() {
@@ -1637,7 +1650,7 @@
     }
 
     private int runClear() {
-        int userId = 0;
+        int userId = UserHandle.USER_OWNER;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1709,7 +1722,7 @@
     }
 
     private int runSetEnabledSetting(int state) {
-        int userId = 0;
+        int userId = UserHandle.USER_OWNER;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1758,7 +1771,7 @@
     }
 
     private int runSetHiddenSetting(boolean state) {
-        int userId = 0;
+        int userId = UserHandle.USER_OWNER;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1963,9 +1976,9 @@
      * Displays the package file for a package.
      * @param pckg
      */
-    private int displayPackageFilePath(String pckg) {
+    private int displayPackageFilePath(String pckg, int userId) {
         try {
-            PackageInfo info = mPm.getPackageInfo(pckg, 0, 0);
+            PackageInfo info = mPm.getPackageInfo(pckg, 0, userId);
             if (info != null && info.applicationInfo != null) {
                 System.out.print("package:");
                 System.out.println(info.applicationInfo.sourceDir);
@@ -2104,7 +2117,7 @@
         System.err.println("       pm list features");
         System.err.println("       pm list libraries");
         System.err.println("       pm list users");
-        System.err.println("       pm path PACKAGE");
+        System.err.println("       pm path [--user USER_ID] PACKAGE");
         System.err.println("       pm dump PACKAGE");
         System.err.println("       pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]");
         System.err.println("       pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 26c886e..0b751b2 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -23,9 +23,9 @@
 import android.graphics.PointF;
 import android.util.Log;
 import android.util.Property;
+import android.view.animation.AccelerateDecelerateInterpolator;
 
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 
 /**
  * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
@@ -53,7 +53,8 @@
  * from the target object when the animator starts, just like animators with only one
  * value specified. In addition, an optional interpolator can be specified. The interpolator will
  * be applied on the interval between the keyframe that the interpolator is set on and the previous
- * keyframe. When no interpolator is supplied, the default linear interpolator will be used. </p>
+ * keyframe. When no interpolator is supplied, the default {@link AccelerateDecelerateInterpolator}
+ * will be used. </p>
  *
  * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf_interpolated.xml KeyframeResources}
  *
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d47c0aa..2f0849f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -705,6 +705,8 @@
     private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
     private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
     private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_";
+    private static final String HAS_CURENT_PERMISSIONS_REQUEST_KEY =
+            "android:hasCurrentPermissionsRequest";
 
     private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
 
@@ -813,6 +815,8 @@
     SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK;
     SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK;
 
+    private boolean mHasCurrentPermissionsRequest;
+
     /** Return the intent that started this activity. */
     public Intent getIntent() {
         return mIntent;
@@ -1314,6 +1318,7 @@
         onSaveInstanceState(outState);
         saveManagedDialogs(outState);
         mActivityTransitionState.saveState(outState);
+        storeHasCurrentPermissionRequest(outState);
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
     }
 
@@ -1329,6 +1334,7 @@
     final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
         onSaveInstanceState(outState, outPersistentState);
         saveManagedDialogs(outState);
+        storeHasCurrentPermissionRequest(outState);
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
                 ", " + outPersistentState);
     }
@@ -3862,8 +3868,15 @@
      * @see #shouldShowRequestPermissionRationale(String)
      */
     public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
+        if (mHasCurrentPermissionsRequest) {
+            Log.w(TAG, "Can reqeust only one set of permissions at a time");
+            // Dispatch the callback with empty arrays which means a cancellation.
+            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
+            return;
+        }
         Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
         startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
+        mHasCurrentPermissionsRequest = true;
     }
 
     /**
@@ -6301,12 +6314,14 @@
     }
 
     final void performCreate(Bundle icicle) {
+        restoreHasCurrentPermissionRequest(icicle);
         onCreate(icicle);
         mActivityTransitionState.readState(icicle);
         performCreateCommon();
     }
 
     final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+        restoreHasCurrentPermissionRequest(icicle);
         onCreate(icicle, persistentState);
         mActivityTransitionState.readState(icicle);
         performCreateCommon();
@@ -6489,6 +6504,19 @@
         return mResumed;
     }
 
+    private void storeHasCurrentPermissionRequest(Bundle bundle) {
+        if (bundle != null && mHasCurrentPermissionsRequest) {
+            bundle.putBoolean(HAS_CURENT_PERMISSIONS_REQUEST_KEY, true);
+        }
+    }
+
+    private void restoreHasCurrentPermissionRequest(Bundle bundle) {
+        if (bundle != null) {
+            mHasCurrentPermissionsRequest = bundle.getBoolean(
+                    HAS_CURENT_PERMISSIONS_REQUEST_KEY, false);
+        }
+    }
+
     void dispatchActivityResult(String who, int requestCode,
         int resultCode, Intent data) {
         if (false) Log.v(
@@ -6616,6 +6644,7 @@
     }
 
     private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
+        mHasCurrentPermissionsRequest = false;
         // If the package installer crashed we may have not data - best effort.
         String[] permissions = (data != null) ? data.getStringArrayExtra(
                 PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 61a9a84..3bfeff0 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -204,17 +204,10 @@
 
     /**
      * Flag for IActivityManaqer.startActivity: launch the app for
-     * OpenGL tracing.
-     * @hide
-     */
-    public static final int START_FLAG_OPENGL_TRACES = 1<<2;
-
-    /**
-     * Flag for IActivityManaqer.startActivity: launch the app for
      * allocation tracking.
      * @hide
      */
-    public static final int START_FLAG_TRACK_ALLOCATION = 1<<3;
+    public static final int START_FLAG_TRACK_ALLOCATION = 1<<2;
 
     /**
      * Result for IActivityManaqer.broadcastIntent: success!
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index beaf3cf..b9292de 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -469,7 +469,6 @@
         IUiAutomationConnection instrumentationUiAutomationConnection;
         int debugMode;
         boolean enableBinderTracking;
-        boolean enableOpenGlTrace;
         boolean trackAllocation;
         boolean restrictedBackupMode;
         boolean persistent;
@@ -805,10 +804,9 @@
                 ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                 IInstrumentationWatcher instrumentationWatcher,
                 IUiAutomationConnection instrumentationUiConnection, int debugMode,
-                boolean enableBinderTracking, boolean enableOpenGlTrace,
-                boolean trackAllocation, boolean isRestrictedBackupMode,
-                boolean persistent, Configuration config, CompatibilityInfo compatInfo,
-                Map<String, IBinder> services, Bundle coreSettings) {
+                boolean enableBinderTracking, boolean trackAllocation,
+                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
+                CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {
 
             if (services != null) {
                 // Setup the service cache in the ServiceManager
@@ -864,7 +862,6 @@
             data.instrumentationUiAutomationConnection = instrumentationUiConnection;
             data.debugMode = debugMode;
             data.enableBinderTracking = enableBinderTracking;
-            data.enableOpenGlTrace = enableOpenGlTrace;
             data.trackAllocation = trackAllocation;
             data.restrictedBackupMode = isRestrictedBackupMode;
             data.persistent = persistent;
@@ -4776,11 +4773,6 @@
             }
         }
 
-        // Enable OpenGL tracing if required
-        if (data.enableOpenGlTrace) {
-            GLUtils.setTracingLevel(1);
-        }
-
         // Allow application-generated systrace messages if we're debuggable.
         boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         Trace.setAppTracingAllowed(isAppDebuggable);
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index bead625..bfd9ca5 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -294,7 +294,6 @@
                     IUiAutomationConnection.Stub.asInterface(binder);
             int testMode = data.readInt();
             boolean enableBinderTracking = data.readInt() != 0;
-            boolean openGlTrace = data.readInt() != 0;
             boolean trackAllocation = data.readInt() != 0;
             boolean restrictedBackupMode = (data.readInt() != 0);
             boolean persistent = (data.readInt() != 0);
@@ -304,8 +303,8 @@
             Bundle coreSettings = data.readBundle();
             bindApplication(packageName, info, providers, testName, profilerInfo, testArgs,
                     testWatcher, uiAutomationConnection, testMode, enableBinderTracking,
-                    openGlTrace, trackAllocation, restrictedBackupMode, persistent, config,
-                    compatInfo, services, coreSettings);
+                    trackAllocation, restrictedBackupMode, persistent, config, compatInfo, services,
+                    coreSettings);
             return true;
         }
 
@@ -1020,10 +1019,9 @@
             List<ProviderInfo> providers, ComponentName testName, ProfilerInfo profilerInfo,
             Bundle testArgs, IInstrumentationWatcher testWatcher,
             IUiAutomationConnection uiAutomationConnection, int debugMode,
-            boolean enableBinderTracking, boolean openGlTrace, boolean trackAllocation,
-            boolean restrictedBackupMode, boolean persistent, Configuration config,
-            CompatibilityInfo compatInfo, Map<String, IBinder> services,
-            Bundle coreSettings) throws RemoteException {
+            boolean enableBinderTracking, boolean trackAllocation, boolean restrictedBackupMode,
+            boolean persistent, Configuration config, CompatibilityInfo compatInfo,
+            Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
         Parcel data = Parcel.obtain();
         data.writeInterfaceToken(IApplicationThread.descriptor);
         data.writeString(packageName);
@@ -1046,7 +1044,6 @@
         data.writeStrongInterface(uiAutomationConnection);
         data.writeInt(debugMode);
         data.writeInt(enableBinderTracking ? 1 : 0);
-        data.writeInt(openGlTrace ? 1 : 0);
         data.writeInt(trackAllocation ? 1 : 0);
         data.writeInt(restrictedBackupMode ? 1 : 0);
         data.writeInt(persistent ? 1 : 0);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index b44aab7..e038a72 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -465,9 +465,6 @@
     // If set this fragment is being retained across the current config change.
     boolean mRetaining;
 
-    // If set this fragment's loaders are being retained across the current config change.
-    boolean mRetainLoader;
-
     // If set this fragment has menu items to contribute.
     boolean mHasMenu;
 
@@ -2415,7 +2412,7 @@
                 mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
             }
             if (mLoaderManager != null) {
-                if (mRetainLoader) {
+                if (mHost.getRetainLoaders()) {
                     mLoaderManager.doRetain();
                 } else {
                     mLoaderManager.doStop();
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 1b45137..28dadfa 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -341,7 +341,6 @@
      */
     public void doLoaderStop(boolean retain) {
         mHost.doLoaderStop(retain);
-        mHost.mFragmentManager.setRetainLoader(retain);
     }
 
     /**
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 7b01307..13517e6 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -42,9 +42,14 @@
     private final Handler mHandler;
     final int mWindowAnimations;
     final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
+    /** The loader managers for individual fragments [i.e. Fragment#getLoaderManager()] */
     private ArrayMap<String, LoaderManager> mAllLoaderManagers;
+    /** Whether or not fragment loaders should retain their state */
+    private boolean mRetainLoaders;
+    /** The loader manger for the fragment host [i.e. Activity#getLoaderManager()] */
     private LoaderManagerImpl mLoaderManager;
     private boolean mCheckedForLoaderManager;
+    /** Whether or not the fragment host loader manager was started */
     private boolean mLoadersStarted;
 
     public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
@@ -166,6 +171,10 @@
         return true;
     }
 
+    boolean getRetainLoaders() {
+        return mRetainLoaders;
+    }
+
     Activity getActivity() {
         return mActivity;
     }
@@ -217,6 +226,8 @@
     }
 
     void doLoaderStop(boolean retain) {
+        mRetainLoaders = retain;
+
         if (mLoaderManager == null) {
             return;
         }
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 51d6132..696ccdb 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -869,17 +869,6 @@
         }
     }
 
-    void setRetainLoader(boolean retain) {
-        if (mActive != null) {
-            for (int i=0; i<mActive.size(); i++) {
-                Fragment f = mActive.get(i);
-                if (f != null) {
-                    f.mRetainLoader = retain;
-                }
-            }
-        }
-    }
-
     void moveToState(Fragment f, int newState, int transit, int transitionStyle,
             boolean keepActive) {
         if (DEBUG && false) Log.v(TAG, "moveToState: " + f
@@ -2221,6 +2210,7 @@
             // This fragment was retained from a previous instance; get it
             // going now.
             fragment.mInLayout = true;
+            fragment.mHost = mHost;
             // If this fragment is newly instantiated (either right now, or
             // from last saved state), then give it the attributes to
             // initialize itself.
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 876c0f6..0249cb2 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -1,8 +1,16 @@
 package android.app;
 
-import android.util.Pair;
+import android.annotation.AnimatorRes;
+import android.annotation.IdRes;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
 import android.view.View;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * API for performing a set of Fragment operations.
  *
@@ -21,7 +29,7 @@
     /**
      * Calls {@link #add(int, Fragment, String)} with a null tag.
      */
-    public abstract FragmentTransaction add(int containerViewId, Fragment fragment);
+    public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);
     
     /**
      * Add a fragment to the activity state.  This fragment may optionally
@@ -38,12 +46,13 @@
      * 
      * @return Returns the same FragmentTransaction instance.
      */
-    public abstract FragmentTransaction add(int containerViewId, Fragment fragment, String tag);
+    public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment,
+            String tag);
     
     /**
      * Calls {@link #replace(int, Fragment, String)} with a null tag.
      */
-    public abstract FragmentTransaction replace(int containerViewId, Fragment fragment);
+    public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment);
     
     /**
      * Replace an existing fragment that was added to a container.  This is
@@ -61,7 +70,8 @@
      * 
      * @return Returns the same FragmentTransaction instance.
      */
-    public abstract FragmentTransaction replace(int containerViewId, Fragment fragment, String tag);
+    public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment,
+            String tag);
     
     /**
      * Remove an existing fragment.  If it was added to a container, its view
@@ -148,12 +158,18 @@
      * with it except that it is appearing or disappearing for some reason. */
     public static final int TRANSIT_FRAGMENT_FADE = 3 | TRANSIT_ENTER_MASK;
 
+    /** @hide */
+    @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Transit {}
+
     /**
      * Set specific animation resources to run for the fragments that are
      * entering and exiting in this transaction. These animations will not be
      * played when popping the back stack.
      */
-    public abstract FragmentTransaction setCustomAnimations(int enter, int exit);
+    public abstract FragmentTransaction setCustomAnimations(@AnimatorRes int enter,
+            @AnimatorRes int exit);
 
     /**
      * Set specific animation resources to run for the fragments that are
@@ -161,15 +177,15 @@
      * and <code>popExit</code> animations will be played for enter/exit
      * operations specifically when popping the back stack.
      */
-    public abstract FragmentTransaction setCustomAnimations(int enter, int exit,
-            int popEnter, int popExit);
+    public abstract FragmentTransaction setCustomAnimations(@AnimatorRes int enter,
+            @AnimatorRes int exit, @AnimatorRes int popEnter, @AnimatorRes int popExit);
 
     /**
      * Select a standard transition animation for this transaction.  May be
      * one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
-     * or {@link #TRANSIT_FRAGMENT_CLOSE}
+     * {@link #TRANSIT_FRAGMENT_CLOSE}, or {@link #TRANSIT_FRAGMENT_FADE}.
      */
-    public abstract FragmentTransaction setTransition(int transit);
+    public abstract FragmentTransaction setTransition(@Transit int transit);
 
     /**
      * Used with to map a View from a removed or hidden Fragment to a View from a shown
@@ -185,7 +201,7 @@
      * Set a custom style resource that will be used for resolving transit
      * animations.
      */
-    public abstract FragmentTransaction setTransitionStyle(int styleRes);
+    public abstract FragmentTransaction setTransitionStyle(@StyleRes int styleRes);
     
     /**
      * Add this transaction to the back stack.  This means that the transaction
@@ -194,7 +210,7 @@
      *
      * @param name An optional name for this back stack state, or null.
      */
-    public abstract FragmentTransaction addToBackStack(String name);
+    public abstract FragmentTransaction addToBackStack(@Nullable String name);
 
     /**
      * Returns true if this FragmentTransaction is allowed to be added to the back
@@ -218,7 +234,7 @@
      *
      * @param res A string resource containing the title.
      */
-    public abstract FragmentTransaction setBreadCrumbTitle(int res);
+    public abstract FragmentTransaction setBreadCrumbTitle(@StringRes int res);
 
     /**
      * Like {@link #setBreadCrumbTitle(int)} but taking a raw string; this
@@ -233,7 +249,7 @@
      *
      * @param res A string resource containing the title.
      */
-    public abstract FragmentTransaction setBreadCrumbShortTitle(int res);
+    public abstract FragmentTransaction setBreadCrumbShortTitle(@StringRes int res);
 
     /**
      * Like {@link #setBreadCrumbShortTitle(int)} but taking a raw string; this
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 2d78e19..99e8853 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -96,10 +96,10 @@
     void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
             ComponentName testName, ProfilerInfo profilerInfo, Bundle testArguments,
             IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
-            int debugMode, boolean enableBinderTracking, boolean openGlTrace,
-            boolean trackAllocation, boolean restrictedBackupMode, boolean persistent,
-            Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
-            Bundle coreSettings) throws RemoteException;
+            int debugMode, boolean enableBinderTracking, boolean trackAllocation,
+            boolean restrictedBackupMode, boolean persistent, Configuration config,
+            CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings)
+            throws RemoteException;
     void scheduleExit() throws RemoteException;
     void scheduleSuicide() throws RemoteException;
     void scheduleConfigurationChanged(Configuration config) throws RemoteException;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 55aec59..db18722 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -62,6 +62,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A class that represents how a persistent notification is to be presented to
@@ -1362,6 +1363,95 @@
     public Notification publicVersion;
 
     /**
+     * Structure to encapsulate a topic that is shown in Notification settings.
+     * It must include an id and label.
+     */
+    public static class Topic implements Parcelable {
+        private final String id;
+        private final CharSequence label;
+
+        public Topic(String id, CharSequence label) {
+            this.id = id;
+            this.label = safeCharSequence(label);
+        }
+
+        private Topic(Parcel in) {
+            if (in.readInt() != 0) {
+                id = in.readString();
+            } else {
+                id = null;
+            }
+            label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        public CharSequence getLabel() {
+            return label;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder(Topic.class.getSimpleName()).append('[')
+                    .append("id=").append(id)
+                    .append(",label=").append(label)
+                    .append(']').toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Topic)) return false;
+            if (o == this) return true;
+            final Topic other = (Topic) o;
+            return Objects.equals(other.id, id)
+                    && Objects.equals(other.label, label);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(id, label);
+        }
+
+        @Override
+        public Topic clone() {
+            return new Topic(id, label);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            if (id != null) {
+                out.writeInt(1);
+                out.writeString(id);
+            } else {
+                out.writeInt(0);
+            }
+            TextUtils.writeToParcel(label, out, flags);
+        }
+        public static final Parcelable.Creator<Topic> CREATOR =
+                new Parcelable.Creator<Topic>() {
+                    public Topic createFromParcel(Parcel in) {
+                        return new Topic(in);
+                    }
+                    public Topic[] newArray(int size) {
+                        return new Topic[size];
+                    }
+                };
+    }
+
+    private Topic[] topics;
+
+    public Topic[] getTopics() {
+        return topics;
+    }
+
+    /**
      * Constructs a Notification object with default values.
      * You might want to consider using {@link Builder} instead.
      */
@@ -1487,6 +1577,8 @@
         }
 
         color = parcel.readInt();
+
+        topics = parcel.createTypedArray(Topic.CREATOR); // may be null
     }
 
     @Override
@@ -1587,6 +1679,13 @@
 
         that.color = this.color;
 
+        if (this.topics != null) {
+            that.topics = new Topic[this.topics.length];
+            for(int i=0; i<this.topics.length; i++) {
+                that.topics[i] = this.topics[i].clone();
+            }
+        }
+
         if (!heavy) {
             that.lightenPayload(); // will clean out extras
         }
@@ -1759,6 +1858,8 @@
         }
 
         parcel.writeInt(color);
+
+        parcel.writeTypedArray(topics, 0); // null ok
     }
 
     /**
@@ -1895,6 +1996,18 @@
             sb.append(" publicVersion=");
             sb.append(publicVersion.toString());
         }
+        if (topics != null) {
+            sb.append("topics=[");
+            int N = topics.length;
+            if (N > 0) {
+                for (int i = 0; i < N-1; i++) {
+                    sb.append(topics[i]);
+                    sb.append(',');
+                }
+                sb.append(topics[N-1]);
+            }
+            sb.append("]");
+        }
         sb.append(")");
         return sb.toString();
     }
@@ -2105,6 +2218,7 @@
         private final NotificationColorUtil mColorUtil;
         private ArrayList<String> mPeople;
         private int mColor = COLOR_DEFAULT;
+        private List<Topic> mTopics = new ArrayList<>();
 
         /**
          * The user that built the notification originally.
@@ -2874,6 +2988,19 @@
             return this;
         }
 
+        /**
+         * Add a topic to this notification. Topics are typically displayed in Notification
+         * settings.
+         * <p>
+         * Every topic must have an id and a textual label.
+         *
+         * @param topic The topic to add.
+         */
+        public Builder addTopic(Topic topic) {
+            mTopics.add(topic);
+            return this;
+        }
+
         private Drawable getProfileBadgeDrawable() {
             // Note: This assumes that the current user can read the profile badge of the
             // originating user.
@@ -3364,6 +3491,10 @@
                 n.publicVersion = new Notification();
                 mPublicVersion.cloneInto(n.publicVersion, true);
             }
+            if (mTopics.size() > 0) {
+                n.topics = new Topic[mTopics.size()];
+                mTopics.toArray(n.topics);
+            }
             // Note: If you're adding new fields, also update restoreFromNotitification().
             return n;
         }
@@ -3605,6 +3736,10 @@
 
             mPublicVersion = n.publicVersion;
 
+            if (n.topics != null) {
+                Collections.addAll(mTopics, n.topics);
+            }
+
             // Extras.
             Bundle extras = n.extras;
             mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index e5cfbf3..3f02f17 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -647,20 +647,18 @@
 
     /**
      * Open a private file associated with this Context's application package
-     * for writing.  Creates the file if it doesn't already exist.
-     *
-     * <p>No permissions are required to invoke this method, since it uses internal
-     * storage.
+     * for writing. Creates the file if it doesn't already exist.
+     * <p>
+     * No additional permissions are required for the calling app to read or
+     * write the returned file.
      *
      * @param name The name of the file to open; can not contain path
-     *             separators.
-     * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
-     * default operation, {@link #MODE_APPEND} to append to an existing file,
-     * {@link #MODE_WORLD_READABLE} and {@link #MODE_WORLD_WRITEABLE} to control
-     * permissions.
-     *
+     *            separators.
+     * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the
+     *            default operation, {@link #MODE_APPEND} to append to an
+     *            existing file, {@link #MODE_WORLD_READABLE} and
+     *            {@link #MODE_WORLD_WRITEABLE} to control permissions.
      * @return The resulting {@link FileOutputStream}.
-     *
      * @see #MODE_APPEND
      * @see #MODE_PRIVATE
      * @see #MODE_WORLD_READABLE
@@ -693,6 +691,9 @@
     /**
      * Returns the absolute path on the filesystem where a file created with
      * {@link #openFileOutput} is stored.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
      *
      * @param name The name of the file for which you would like to get
      *          its path.
@@ -706,14 +707,16 @@
     public abstract File getFileStreamPath(String name);
 
     /**
-     * Returns the absolute path to the directory on the filesystem where
-     * files created with {@link #openFileOutput} are stored.
-     *
-     * <p>No permissions are required to read or write to the returned path, since this
-     * path is internal storage.
+     * Returns the absolute path to the directory on the filesystem where files
+     * created with {@link #openFileOutput} are stored.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path.
      *
      * @return The path of the directory holding application files.
-     *
      * @see #openFileOutput
      * @see #getFileStreamPath
      * @see #getDir
@@ -722,17 +725,19 @@
 
     /**
      * Returns the absolute path to the directory on the filesystem similar to
-     * {@link #getFilesDir()}.  The difference is that files placed under this
-     * directory will be excluded from automatic backup to remote storage.  See
+     * {@link #getFilesDir()}. The difference is that files placed under this
+     * directory will be excluded from automatic backup to remote storage. See
      * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
      * of the automatic backup mechanism in Android.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path.
      *
-     * <p>No permissions are required to read or write to the returned path, since this
-     * path is internal storage.
-     *
-     * @return The path of the directory holding application files that will not be
-     *         automatically backed up to remote storage.
-     *
+     * @return The path of the directory holding application files that will not
+     *         be automatically backed up to remote storage.
      * @see #openFileOutput
      * @see #getFileStreamPath
      * @see #getDir
@@ -741,200 +746,256 @@
     public abstract File getNoBackupFilesDir();
 
     /**
-     * Returns the absolute path to the directory on the primary external filesystem
-     * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
-     * Environment.getExternalStorageDirectory()}) where the application can
-     * place persistent files it owns.  These files are internal to the
-     * applications, and not typically visible to the user as media.
-     *
-     * <p>This is like {@link #getFilesDir()} in that these
-     * files will be deleted when the application is uninstalled, however there
-     * are some important differences:
-     *
+     * Returns the absolute path to the directory on the primary shared/external
+     * storage device where the application can place persistent files it owns.
+     * These files are internal to the applications, and not typically visible
+     * to the user as media.
+     * <p>
+     * This is like {@link #getFilesDir()} in that these files will be deleted
+     * when the application is uninstalled, however there are some important
+     * differences:
      * <ul>
-     * <li>External files are not always available: they will disappear if the
-     * user mounts the external storage on a computer or removes it.  See the
-     * APIs on {@link android.os.Environment} for information in the storage state.
-     * <li>There is no security enforced with these files.  For example, any application
-     * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * <li>Shared storage may not always be available, since removable media can
+     * be ejected by the user. Media state can be checked using
+     * {@link Environment#getExternalStorageState(File)}.
+     * <li>There is no security enforced with these files. For example, any
+     * application holding
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
      * these files.
      * </ul>
-     *
-     * <p>Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * <p>
+     * If a shared storage device is emulated (as determined by
+     * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+     * backed by a private user data partition, which means there is little
+     * benefit to storing data here instead of the private directories returned
+     * by {@link #getFilesDir()}, etc.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
      * are required to read or write to the returned path; it's always
-     * accessible to the calling app.  This only applies to paths generated for
-     * package name of the calling application.  To access paths belonging
-     * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
-     * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
-     *
-     * <p>On devices with multiple users (as described by {@link UserManager}),
-     * each user has their own isolated external storage. Applications only
-     * have access to the external storage for the user they're running as.</p>
-     *
-     * <p>Here is an example of typical code to manipulate a file in
-     * an application's private storage:</p>
-     *
+     * accessible to the calling app. This only applies to paths generated for
+     * package name of the calling application. To access paths belonging to
+     * other packages,
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+     * <p>
+     * On devices with multiple users (as described by {@link UserManager}),
+     * each user has their own isolated shared storage. Applications only have
+     * access to the shared storage for the user they're running as.
+     * <p>
+     * The returned path may change over time if different shared storage media
+     * is inserted, so only relative paths should be persisted.
+     * <p>
+     * Here is an example of typical code to manipulate a file in an
+     * application's shared storage:
+     * </p>
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * private_file}
-     *
-     * <p>If you supply a non-null <var>type</var> to this function, the returned
-     * file will be a path to a sub-directory of the given type.  Though these files
-     * are not automatically scanned by the media scanner, you can explicitly
-     * add them to the media database with
-     * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[],
-     *      android.media.MediaScannerConnection.OnScanCompletedListener)
-     *      MediaScannerConnection.scanFile}.
-     * Note that this is not the same as
+     * <p>
+     * If you supply a non-null <var>type</var> to this function, the returned
+     * file will be a path to a sub-directory of the given type. Though these
+     * files are not automatically scanned by the media scanner, you can
+     * explicitly add them to the media database with
+     * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[], android.media.MediaScannerConnection.OnScanCompletedListener)
+     * MediaScannerConnection.scanFile}. Note that this is not the same as
      * {@link android.os.Environment#getExternalStoragePublicDirectory
      * Environment.getExternalStoragePublicDirectory()}, which provides
-     * directories of media shared by all applications.  The
-     * directories returned here are
-     * owned by the application, and their contents will be removed when the
-     * application is uninstalled.  Unlike
+     * directories of media shared by all applications. The directories returned
+     * here are owned by the application, and their contents will be removed
+     * when the application is uninstalled. Unlike
      * {@link android.os.Environment#getExternalStoragePublicDirectory
-     * Environment.getExternalStoragePublicDirectory()}, the directory
-     * returned here will be automatically created for you.
-     *
-     * <p>Here is an example of typical code to manipulate a picture in
-     * an application's private storage and add it to the media database:</p>
-     *
+     * Environment.getExternalStoragePublicDirectory()}, the directory returned
+     * here will be automatically created for you.
+     * <p>
+     * Here is an example of typical code to manipulate a picture in an
+     * application's shared storage and add it to the media database:
+     * </p>
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * private_picture}
      *
-     * @param type The type of files directory to return.  May be null for
-     * the root of the files directory or one of
-     * the following Environment constants for a subdirectory:
-     * {@link android.os.Environment#DIRECTORY_MUSIC},
-     * {@link android.os.Environment#DIRECTORY_PODCASTS},
-     * {@link android.os.Environment#DIRECTORY_RINGTONES},
-     * {@link android.os.Environment#DIRECTORY_ALARMS},
-     * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
-     * {@link android.os.Environment#DIRECTORY_PICTURES}, or
-     * {@link android.os.Environment#DIRECTORY_MOVIES}.
-     *
-     * @return The path of the directory holding application files
-     * on external storage.  Returns null if external storage is not currently
-     * mounted so it could not ensure the path exists; you will need to call
-     * this method again when it is available.
-     *
+     * @param type The type of files directory to return. May be {@code null}
+     *            for the root of the files directory or one of the following
+     *            constants for a subdirectory:
+     *            {@link android.os.Environment#DIRECTORY_MUSIC},
+     *            {@link android.os.Environment#DIRECTORY_PODCASTS},
+     *            {@link android.os.Environment#DIRECTORY_RINGTONES},
+     *            {@link android.os.Environment#DIRECTORY_ALARMS},
+     *            {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
+     *            {@link android.os.Environment#DIRECTORY_PICTURES}, or
+     *            {@link android.os.Environment#DIRECTORY_MOVIES}.
+     * @return the absolute path to application-specific directory. May return
+     *         {@code null} if shared storage is not currently available.
      * @see #getFilesDir
-     * @see android.os.Environment#getExternalStoragePublicDirectory
+     * @see #getExternalFilesDirs(String)
+     * @see Environment#getExternalStorageState(File)
+     * @see Environment#isExternalStorageEmulated(File)
+     * @see Environment#isExternalStorageRemovable(File)
      */
     @Nullable
     public abstract File getExternalFilesDir(@Nullable String type);
 
     /**
      * Returns absolute paths to application-specific directories on all
-     * external storage devices where the application can place persistent files
-     * it owns. These files are internal to the application, and not typically
-     * visible to the user as media.
+     * shared/external storage devices where the application can place
+     * persistent files it owns. These files are internal to the application,
+     * and not typically visible to the user as media.
      * <p>
-     * This is like {@link #getFilesDir()} in that these files will be deleted when
-     * the application is uninstalled, however there are some important differences:
+     * This is like {@link #getFilesDir()} in that these files will be deleted
+     * when the application is uninstalled, however there are some important
+     * differences:
      * <ul>
-     * <li>External files are not always available: they will disappear if the
-     * user mounts the external storage on a computer or removes it.
-     * <li>There is no security enforced with these files.
+     * <li>Shared storage may not always be available, since removable media can
+     * be ejected by the user. Media state can be checked using
+     * {@link Environment#getExternalStorageState(File)}.
+     * <li>There is no security enforced with these files. For example, any
+     * application holding
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * these files.
      * </ul>
      * <p>
-     * External storage devices returned here are considered a permanent part of
-     * the device, including both emulated external storage and physical media
-     * slots, such as SD cards in a battery compartment. The returned paths do
-     * not include transient devices, such as USB flash drives.
+     * If a shared storage device is emulated (as determined by
+     * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+     * backed by a private user data partition, which means there is little
+     * benefit to storing data here instead of the private directories returned
+     * by {@link #getFilesDir()}, etc.
      * <p>
-     * An application may store data on any or all of the returned devices.  For
+     * Shared storage devices returned here are considered a stable part of the
+     * device, including physical media slots under a protective cover. The
+     * returned paths do not include transient devices, such as USB flash drives
+     * connected to handheld devices.
+     * <p>
+     * An application may store data on any or all of the returned devices. For
      * example, an app may choose to store large files on the device with the
      * most available space, as measured by {@link StatFs}.
      * <p>
-     * No permissions are required to read or write to the returned paths; they
-     * are always accessible to the calling app.  Write access outside of these
-     * paths on secondary external storage devices is not available.
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path. Write access outside of these paths
+     * on secondary external storage devices is not available.
      * <p>
-     * The first path returned is the same as {@link #getExternalFilesDir(String)}.
-     * Returned paths may be {@code null} if a storage device is unavailable.
+     * The returned path may change over time if different shared storage media
+     * is inserted, so only relative paths should be persisted.
      *
+     * @param type The type of files directory to return. May be {@code null}
+     *            for the root of the files directory or one of the following
+     *            constants for a subdirectory:
+     *            {@link android.os.Environment#DIRECTORY_MUSIC},
+     *            {@link android.os.Environment#DIRECTORY_PODCASTS},
+     *            {@link android.os.Environment#DIRECTORY_RINGTONES},
+     *            {@link android.os.Environment#DIRECTORY_ALARMS},
+     *            {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
+     *            {@link android.os.Environment#DIRECTORY_PICTURES}, or
+     *            {@link android.os.Environment#DIRECTORY_MOVIES}.
+     * @return the absolute paths to application-specific directories. Some
+     *         individual paths may be {@code null} if that shared storage is
+     *         not currently available. The first path returned is the same as
+     *         {@link #getExternalFilesDir(String)}.
      * @see #getExternalFilesDir(String)
      * @see Environment#getExternalStorageState(File)
+     * @see Environment#isExternalStorageEmulated(File)
+     * @see Environment#isExternalStorageRemovable(File)
      */
     public abstract File[] getExternalFilesDirs(String type);
 
     /**
-     * Return the primary external storage directory where this application's OBB
-     * files (if there are any) can be found. Note if the application does not have
-     * any OBB files, this directory may not exist.
+     * Return the primary shared/external storage directory where this
+     * application's OBB files (if there are any) can be found. Note if the
+     * application does not have any OBB files, this directory may not exist.
      * <p>
-     * This is like {@link #getFilesDir()} in that these files will be deleted when
-     * the application is uninstalled, however there are some important differences:
+     * This is like {@link #getFilesDir()} in that these files will be deleted
+     * when the application is uninstalled, however there are some important
+     * differences:
      * <ul>
-     * <li>External files are not always available: they will disappear if the
-     * user mounts the external storage on a computer or removes it.
-     * <li>There is no security enforced with these files.  For example, any application
-     * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * <li>Shared storage may not always be available, since removable media can
+     * be ejected by the user. Media state can be checked using
+     * {@link Environment#getExternalStorageState(File)}.
+     * <li>There is no security enforced with these files. For example, any
+     * application holding
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
      * these files.
      * </ul>
      * <p>
      * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
      * are required to read or write to the returned path; it's always
-     * accessible to the calling app.  This only applies to paths generated for
-     * package name of the calling application.  To access paths belonging
-     * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
-     * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+     * accessible to the calling app. This only applies to paths generated for
+     * package name of the calling application. To access paths belonging to
+     * other packages,
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
      * <p>
      * On devices with multiple users (as described by {@link UserManager}),
      * multiple users may share the same OBB storage location. Applications
      * should ensure that multiple instances running under different users don't
      * interfere with each other.
+     *
+     * @return the absolute path to application-specific directory. May return
+     *         {@code null} if shared storage is not currently available.
+     * @see #getObbDirs()
+     * @see Environment#getExternalStorageState(File)
+     * @see Environment#isExternalStorageEmulated(File)
+     * @see Environment#isExternalStorageRemovable(File)
      */
     public abstract File getObbDir();
 
     /**
      * Returns absolute paths to application-specific directories on all
-     * external storage devices where the application's OBB files (if there are
-     * any) can be found. Note if the application does not have any OBB files,
-     * these directories may not exist.
+     * shared/external storage devices where the application's OBB files (if
+     * there are any) can be found. Note if the application does not have any
+     * OBB files, these directories may not exist.
      * <p>
-     * This is like {@link #getFilesDir()} in that these files will be deleted when
-     * the application is uninstalled, however there are some important differences:
+     * This is like {@link #getFilesDir()} in that these files will be deleted
+     * when the application is uninstalled, however there are some important
+     * differences:
      * <ul>
-     * <li>External files are not always available: they will disappear if the
-     * user mounts the external storage on a computer or removes it.
-     * <li>There is no security enforced with these files.
+     * <li>Shared storage may not always be available, since removable media can
+     * be ejected by the user. Media state can be checked using
+     * {@link Environment#getExternalStorageState(File)}.
+     * <li>There is no security enforced with these files. For example, any
+     * application holding
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * these files.
      * </ul>
      * <p>
-     * External storage devices returned here are considered a permanent part of
-     * the device, including both emulated external storage and physical media
-     * slots, such as SD cards in a battery compartment. The returned paths do
-     * not include transient devices, such as USB flash drives.
+     * Shared storage devices returned here are considered a stable part of the
+     * device, including physical media slots under a protective cover. The
+     * returned paths do not include transient devices, such as USB flash drives
+     * connected to handheld devices.
      * <p>
-     * An application may store data on any or all of the returned devices.  For
+     * An application may store data on any or all of the returned devices. For
      * example, an app may choose to store large files on the device with the
      * most available space, as measured by {@link StatFs}.
      * <p>
-     * No permissions are required to read or write to the returned paths; they
-     * are always accessible to the calling app.  Write access outside of these
-     * paths on secondary external storage devices is not available.
-     * <p>
-     * The first path returned is the same as {@link #getObbDir()}.
-     * Returned paths may be {@code null} if a storage device is unavailable.
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path. Write access outside of these paths
+     * on secondary external storage devices is not available.
      *
+     * @return the absolute paths to application-specific directories. Some
+     *         individual paths may be {@code null} if that shared storage is
+     *         not currently available. The first path returned is the same as
+     *         {@link #getObbDir()}
      * @see #getObbDir()
      * @see Environment#getExternalStorageState(File)
+     * @see Environment#isExternalStorageEmulated(File)
+     * @see Environment#isExternalStorageRemovable(File)
      */
     public abstract File[] getObbDirs();
 
     /**
-     * Returns the absolute path to the application specific cache directory
-     * on the filesystem. These files will be ones that get deleted first when the
-     * device runs low on storage.
-     * There is no guarantee when these files will be deleted.
-     *
+     * Returns the absolute path to the application specific cache directory on
+     * the filesystem. These files will be ones that get deleted first when the
+     * device runs low on storage. There is no guarantee when these files will
+     * be deleted.
+     * <p>
      * <strong>Note: you should not <em>rely</em> on the system deleting these
      * files for you; you should always have a reasonable maximum, such as 1 MB,
      * for the amount of space you consume with cache files, and prune those
      * files when exceeding that space.</strong>
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
+     * Apps require no extra permissions to read or write to the returned path,
+     * since this path lives in their private storage.
      *
      * @return The path of the directory holding application cache files.
-     *
      * @see #openFileOutput
      * @see #getFileStreamPath
      * @see #getDir
@@ -950,6 +1011,9 @@
      * This location is optimal for storing compiled or optimized code generated
      * by your application at runtime.
      * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
      * Apps require no extra permissions to read or write to the returned path,
      * since this path lives in their private storage.
      *
@@ -958,120 +1022,161 @@
     public abstract File getCodeCacheDir();
 
     /**
-     * Returns the absolute path to the directory on the primary external filesystem
-     * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
-     * Environment.getExternalStorageDirectory()} where the application can
-     * place cache files it owns. These files are internal to the application, and
-     * not typically visible to the user as media.
-     *
-     * <p>This is like {@link #getCacheDir()} in that these
-     * files will be deleted when the application is uninstalled, however there
-     * are some important differences:
-     *
+     * Returns absolute path to application-specific directory on the primary
+     * shared/external storage device where the application can place cache
+     * files it owns. These files are internal to the application, and not
+     * typically visible to the user as media.
+     * <p>
+     * This is like {@link #getCacheDir()} in that these files will be deleted
+     * when the application is uninstalled, however there are some important
+     * differences:
      * <ul>
-     * <li>The platform does not always monitor the space available in external
-     * storage, and thus may not automatically delete these files.  Currently
-     * the only time files here will be deleted by the platform is when running
-     * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
-     * {@link android.os.Environment#isExternalStorageEmulated()
-     * Environment.isExternalStorageEmulated()} returns true.  Note that you should
-     * be managing the maximum space you will use for these anyway, just like
-     * with {@link #getCacheDir()}.
-     * <li>External files are not always available: they will disappear if the
-     * user mounts the external storage on a computer or removes it.  See the
-     * APIs on {@link android.os.Environment} for information in the storage state.
-     * <li>There is no security enforced with these files.  For example, any application
-     * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * <li>The platform does not always monitor the space available in shared
+     * storage, and thus may not automatically delete these files. Apps should
+     * always manage the maximum space used in this location. Currently the only
+     * time files here will be deleted by the platform is when running on
+     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
+     * {@link Environment#isExternalStorageEmulated(File)} returns true.
+     * <li>Shared storage may not always be available, since removable media can
+     * be ejected by the user. Media state can be checked using
+     * {@link Environment#getExternalStorageState(File)}.
+     * <li>There is no security enforced with these files. For example, any
+     * application holding
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
      * these files.
      * </ul>
-     *
-     * <p>Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * <p>
+     * If a shared storage device is emulated (as determined by
+     * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+     * backed by a private user data partition, which means there is little
+     * benefit to storing data here instead of the private directory returned by
+     * {@link #getCacheDir()}.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
      * are required to read or write to the returned path; it's always
-     * accessible to the calling app.  This only applies to paths generated for
-     * package name of the calling application.  To access paths belonging
-     * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
-     * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+     * accessible to the calling app. This only applies to paths generated for
+     * package name of the calling application. To access paths belonging to
+     * other packages,
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+     * <p>
+     * On devices with multiple users (as described by {@link UserManager}),
+     * each user has their own isolated shared storage. Applications only have
+     * access to the shared storage for the user they're running as.
+     * <p>
+     * The returned path may change over time if different shared storage media
+     * is inserted, so only relative paths should be persisted.
      *
-     * <p>On devices with multiple users (as described by {@link UserManager}),
-     * each user has their own isolated external storage. Applications only
-     * have access to the external storage for the user they're running as.</p>
-     *
-     * @return The path of the directory holding application cache files
-     * on external storage.  Returns null if external storage is not currently
-     * mounted so it could not ensure the path exists; you will need to call
-     * this method again when it is available.
-     *
+     * @return the absolute path to application-specific directory. May return
+     *         {@code null} if shared storage is not currently available.
      * @see #getCacheDir
+     * @see #getExternalCacheDirs()
+     * @see Environment#getExternalStorageState(File)
+     * @see Environment#isExternalStorageEmulated(File)
+     * @see Environment#isExternalStorageRemovable(File)
      */
     @Nullable
     public abstract File getExternalCacheDir();
 
     /**
      * Returns absolute paths to application-specific directories on all
-     * external storage devices where the application can place cache files it
-     * owns. These files are internal to the application, and not typically
-     * visible to the user as media.
+     * shared/external storage devices where the application can place cache
+     * files it owns. These files are internal to the application, and not
+     * typically visible to the user as media.
      * <p>
-     * This is like {@link #getCacheDir()} in that these files will be deleted when
-     * the application is uninstalled, however there are some important differences:
+     * This is like {@link #getCacheDir()} in that these files will be deleted
+     * when the application is uninstalled, however there are some important
+     * differences:
      * <ul>
-     * <li>External files are not always available: they will disappear if the
-     * user mounts the external storage on a computer or removes it.
-     * <li>There is no security enforced with these files.
+     * <li>The platform does not always monitor the space available in shared
+     * storage, and thus may not automatically delete these files. Apps should
+     * always manage the maximum space used in this location. Currently the only
+     * time files here will be deleted by the platform is when running on
+     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
+     * {@link Environment#isExternalStorageEmulated(File)} returns true.
+     * <li>Shared storage may not always be available, since removable media can
+     * be ejected by the user. Media state can be checked using
+     * {@link Environment#getExternalStorageState(File)}.
+     * <li>There is no security enforced with these files. For example, any
+     * application holding
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * these files.
      * </ul>
      * <p>
-     * External storage devices returned here are considered a permanent part of
-     * the device, including both emulated external storage and physical media
-     * slots, such as SD cards in a battery compartment. The returned paths do
-     * not include transient devices, such as USB flash drives.
+     * If a shared storage device is emulated (as determined by
+     * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+     * backed by a private user data partition, which means there is little
+     * benefit to storing data here instead of the private directory returned by
+     * {@link #getCacheDir()}.
      * <p>
-     * An application may store data on any or all of the returned devices.  For
+     * Shared storage devices returned here are considered a stable part of the
+     * device, including physical media slots under a protective cover. The
+     * returned paths do not include transient devices, such as USB flash drives
+     * connected to handheld devices.
+     * <p>
+     * An application may store data on any or all of the returned devices. For
      * example, an app may choose to store large files on the device with the
      * most available space, as measured by {@link StatFs}.
      * <p>
-     * No permissions are required to read or write to the returned paths; they
-     * are always accessible to the calling app.  Write access outside of these
-     * paths on secondary external storage devices is not available.
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path. Write access outside of these paths
+     * on secondary external storage devices is not available.
      * <p>
-     * The first path returned is the same as {@link #getExternalCacheDir()}.
-     * Returned paths may be {@code null} if a storage device is unavailable.
+     * The returned paths may change over time if different shared storage media
+     * is inserted, so only relative paths should be persisted.
      *
+     * @return the absolute paths to application-specific directories. Some
+     *         individual paths may be {@code null} if that shared storage is
+     *         not currently available. The first path returned is the same as
+     *         {@link #getExternalCacheDir()}.
      * @see #getExternalCacheDir()
      * @see Environment#getExternalStorageState(File)
+     * @see Environment#isExternalStorageEmulated(File)
+     * @see Environment#isExternalStorageRemovable(File)
      */
     public abstract File[] getExternalCacheDirs();
 
     /**
      * Returns absolute paths to application-specific directories on all
-     * external storage devices where the application can place media files.
-     * These files are scanned and made available to other apps through
+     * shared/external storage devices where the application can place media
+     * files. These files are scanned and made available to other apps through
      * {@link MediaStore}.
      * <p>
      * This is like {@link #getExternalFilesDirs} in that these files will be
      * deleted when the application is uninstalled, however there are some
      * important differences:
      * <ul>
-     * <li>External files are not always available: they will disappear if the
-     * user mounts the external storage on a computer or removes it.
-     * <li>There is no security enforced with these files.
+     * <li>Shared storage may not always be available, since removable media can
+     * be ejected by the user. Media state can be checked using
+     * {@link Environment#getExternalStorageState(File)}.
+     * <li>There is no security enforced with these files. For example, any
+     * application holding
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * these files.
      * </ul>
      * <p>
-     * External storage devices returned here are considered a permanent part of
-     * the device, including both emulated external storage and physical media
-     * slots, such as SD cards in a battery compartment. The returned paths do
-     * not include transient devices, such as USB flash drives.
+     * Shared storage devices returned here are considered a stable part of the
+     * device, including physical media slots under a protective cover. The
+     * returned paths do not include transient devices, such as USB flash drives
+     * connected to handheld devices.
      * <p>
      * An application may store data on any or all of the returned devices. For
      * example, an app may choose to store large files on the device with the
      * most available space, as measured by {@link StatFs}.
      * <p>
-     * No permissions are required to read or write to the returned paths; they
-     * are always accessible to the calling app. Write access outside of these
-     * paths on secondary external storage devices is not available.
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path. Write access outside of these paths
+     * on secondary external storage devices is not available.
      * <p>
-     * Returned paths may be {@code null} if a storage device is unavailable.
+     * The returned paths may change over time if different shared storage media
+     * is inserted, so only relative paths should be persisted.
      *
+     * @return the absolute paths to application-specific directories. Some
+     *         individual paths may be {@code null} if that shared storage is
+     *         not currently available.
      * @see Environment#getExternalStorageState(File)
+     * @see Environment#isExternalStorageEmulated(File)
+     * @see Environment#isExternalStorageRemovable(File)
      */
     public abstract File[] getExternalMediaDirs();
 
@@ -1094,6 +1199,12 @@
      * created through a File object will only be accessible by your own
      * application; you can only set the mode of the entire directory, not
      * of individual files.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
+     * Apps require no extra permissions to read or write to the returned path,
+     * since this path lives in their private storage.
      *
      * @param name Name of the directory to retrieve.  This is a directory
      * that is created as part of your application data.
@@ -1177,6 +1288,9 @@
     /**
      * Returns the absolute path on the filesystem where a database created with
      * {@link #openOrCreateDatabase} is stored.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
      *
      * @param name The name of the database for which you would like to get
      *          its path.
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 3efd89a..0441ccc 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -21,6 +21,8 @@
 import android.os.Parcel;
 import android.accounts.Account;
 
+import java.util.Objects;
+
 /**
  * Value type that contains information about a periodic sync.
  */
@@ -144,7 +146,9 @@
             if (!b2.containsKey(key)) {
                 return false;
             }
-            if (!b1.get(key).equals(b2.get(key))) {
+            // Null check. According to ContentResolver#validateSyncExtrasBundle null-valued keys
+            // are allowed in the bundle.
+            if (!Objects.equals(b1.get(key), b2.get(key))) {
                 return false;
             }
         }
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a121b4d..3853772 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -67,7 +67,7 @@
      * The launch mode style requested by the activity.  From the
      * {@link android.R.attr#launchMode} attribute, one of
      * {@link #LAUNCH_MULTIPLE},
-     * {@link #LAUNCH_SINGLE_TOP}, {@link #LAUNCH_SINGLE_TASK}, or 
+     * {@link #LAUNCH_SINGLE_TOP}, {@link #LAUNCH_SINGLE_TASK}, or
      * {@link #LAUNCH_SINGLE_INSTANCE}.
      */
     public int launchMode;
@@ -140,7 +140,7 @@
      * Activity.  From the "permission" attribute.
      */
     public String permission;
-    
+
     /**
      * The affinity this activity has for another task in the system.  The
      * string here is the name of the task, often the package name of the
@@ -148,13 +148,13 @@
      * {@link android.R.attr#taskAffinity} attribute.
      */
     public String taskAffinity;
-    
+
     /**
      * If this is an activity alias, this is the real activity class to run
      * for it.  Otherwise, this is null.
      */
     public String targetActivity;
-    
+
     /**
      * Bit in {@link #flags} indicating whether this activity is able to
      * run in multiple processes.  If
@@ -362,7 +362,7 @@
      * the {@link android.R.attr#screenOrientation} attribute.
      */
     public static final int SCREEN_ORIENTATION_SENSOR = 4;
-  
+
     /**
      * Constant corresponding to <code>nosensor</code> in
      * the {@link android.R.attr#screenOrientation} attribute.
@@ -427,7 +427,7 @@
      * The preferred screen orientation this activity would like to run in.
      * From the {@link android.R.attr#screenOrientation} attribute, one of
      * {@link #SCREEN_ORIENTATION_UNSPECIFIED},
-     * {@link #SCREEN_ORIENTATION_LANDSCAPE}, 
+     * {@link #SCREEN_ORIENTATION_LANDSCAPE},
      * {@link #SCREEN_ORIENTATION_PORTRAIT},
      * {@link #SCREEN_ORIENTATION_USER},
      * {@link #SCREEN_ORIENTATION_BEHIND},
@@ -445,7 +445,7 @@
      */
     @ScreenOrientation
     public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-    
+
     /**
      * Bit in {@link #configChanges} that indicates that the activity
      * can itself handle changes to the IMSI MCC.  Set from the
@@ -552,7 +552,7 @@
      * constant starts at the high bits.
      */
     public static final int CONFIG_FONT_SCALE = 0x40000000;
-    
+
     /** @hide
      * Unfortunately the constants for config changes in native code are
      * different from ActivityInfo. :(  Here are the values we should use for the
@@ -702,7 +702,7 @@
      */
     public int lockTaskLaunchMode;
 
-    public InitialLayout initialLayout;
+    public Layout layout;
 
     public ActivityInfo() {
     }
@@ -724,7 +724,7 @@
         maxRecents = orig.maxRecents;
         resizeable = orig.resizeable;
         lockTaskLaunchMode = orig.lockTaskLaunchMode;
-        initialLayout = orig.initialLayout;
+        layout = orig.layout;
     }
 
     /**
@@ -771,10 +771,10 @@
         }
         pw.println(prefix + "resizeable=" + resizeable + " lockTaskLaunchMode="
                 + lockTaskLaunchModeToString(lockTaskLaunchMode));
-        if (initialLayout != null) {
-            pw.println(prefix + "initialLayout=" + initialLayout.width + "|"
-                    + initialLayout.widthFraction + ", " + initialLayout.height + "|"
-                    + initialLayout.heightFraction + ", " + initialLayout.gravity);
+        if (layout != null) {
+            pw.println(prefix + "initialLayout=" + layout.width + "|"
+                    + layout.widthFraction + ", " + layout.height + "|"
+                    + layout.heightFraction + ", " + layout.gravity);
         }
         super.dumpBack(pw, prefix);
     }
@@ -807,13 +807,14 @@
         dest.writeInt(maxRecents);
         dest.writeInt(resizeable ? 1 : 0);
         dest.writeInt(lockTaskLaunchMode);
-        if (initialLayout != null) {
+        if (layout != null) {
             dest.writeInt(1);
-            dest.writeInt(initialLayout.width);
-            dest.writeFloat(initialLayout.widthFraction);
-            dest.writeInt(initialLayout.height);
-            dest.writeFloat(initialLayout.heightFraction);
-            dest.writeInt(initialLayout.gravity);
+            dest.writeInt(layout.width);
+            dest.writeFloat(layout.widthFraction);
+            dest.writeInt(layout.height);
+            dest.writeFloat(layout.heightFraction);
+            dest.writeInt(layout.gravity);
+            dest.writeInt(layout.minimalSize);
         } else {
             dest.writeInt(0);
         }
@@ -848,26 +849,28 @@
         resizeable = (source.readInt() == 1);
         lockTaskLaunchMode = source.readInt();
         if (source.readInt() == 1) {
-            initialLayout = new InitialLayout(source);
+            layout = new Layout(source);
         }
     }
 
-    public static final class InitialLayout {
-        public InitialLayout(int width, float widthFraction, int height, float heightFraction,
-                int gravity) {
+    public static final class Layout {
+        public Layout(int width, float widthFraction, int height, float heightFraction, int gravity,
+                int minimalSize) {
             this.width = width;
             this.widthFraction = widthFraction;
             this.height = height;
             this.heightFraction = heightFraction;
             this.gravity = gravity;
+            this.minimalSize = minimalSize;
         }
 
-        InitialLayout(Parcel source) {
+        Layout(Parcel source) {
             width = source.readInt();
             widthFraction = source.readFloat();
             height = source.readInt();
             heightFraction = source.readFloat();
             gravity = source.readInt();
+            minimalSize = source.readInt();
         }
 
         public final int width;
@@ -875,5 +878,6 @@
         public final int height;
         public final float heightFraction;
         public final int gravity;
+        public final int minimalSize;
     }
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 3283005..d6d395b 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1042,6 +1042,12 @@
         }
 
         /** {@hide} */
+        @SystemApi
+        public void setInstallFlagsQuick() {
+            installFlags |= PackageManager.INSTALL_QUICK;
+        }
+
+        /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
             pw.printHexPair("installFlags", installFlags);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 82cbbbe..aed1a0b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -43,12 +43,10 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
-import android.text.TextUtils;
 import android.util.AndroidException;
 
 import com.android.internal.util.ArrayUtils;
@@ -431,6 +429,14 @@
     public static final int INSTALL_FORCE_PERMISSION_PROMPT = 0x00000400;
 
     /**
+     * Flag parameter for {@link #installPackage} to indicate that this package is
+     * to be installed quickly.
+     *
+     * @hide
+     */
+    public static final int INSTALL_QUICK = 0x00000800;
+
+    /**
      * Flag parameter for
      * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
      * that you don't want to kill the app containing the component.  Be careful when you set this
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1a8602b..1e85dfb 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,6 +24,7 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
@@ -39,6 +40,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.PatternMatcher;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -128,7 +130,7 @@
         public final String name;
         public final int sdkVersion;
         public final int fileVersion;
-        
+
         public NewPermissionInfo(String name, int sdkVersion, int fileVersion) {
             this.name = name;
             this.sdkVersion = sdkVersion;
@@ -215,10 +217,10 @@
         final int iconRes;
         final int logoRes;
         final int bannerRes;
-        
+
         String tag;
         TypedArray sa;
-        
+
         ParsePackageItemArgs(Package _owner, String[] _outError,
                 int _nameRes, int _labelRes, int _iconRes, int _logoRes, int _bannerRes) {
             owner = _owner;
@@ -230,14 +232,14 @@
             bannerRes = _bannerRes;
         }
     }
-    
+
     static class ParseComponentArgs extends ParsePackageItemArgs {
         final String[] sepProcesses;
         final int processRes;
         final int descriptionRes;
         final int enabledRes;
         int flags;
-        
+
         ParseComponentArgs(Package _owner, String[] _outError,
                 int _nameRes, int _labelRes, int _iconRes, int _logoRes, int _bannerRes,
                 String[] _sepProcesses, int _processRes,
@@ -349,7 +351,7 @@
     private ParseComponentArgs mParseActivityAliasArgs;
     private ParseComponentArgs mParseServiceArgs;
     private ParseComponentArgs mParseProviderArgs;
-    
+
     /** If set to true, we will only allow package files that exactly match
      *  the DTD.  Otherwise, we try to get as much from the package as we
      *  can without failing.  This should normally be set to false, to
@@ -622,6 +624,8 @@
     public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
     public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
     public final static int PARSE_ENFORCE_CODE = 1<<10;
+    // TODO: fix b/25118622; remove this entirely once signature processing is quick
+    public final static int PARSE_SKIP_VERIFICATION = 1<<11;
 
     private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
 
@@ -902,6 +906,7 @@
             }
 
             pkg.volumeUuid = volumeUuid;
+            pkg.applicationInfo.volumeUuid = volumeUuid;
             pkg.baseCodePath = apkPath;
             pkg.mSignatures = null;
 
@@ -920,7 +925,6 @@
     private void parseSplitApk(Package pkg, int splitIndex, AssetManager assets, int flags)
             throws PackageParserException {
         final String apkPath = pkg.splitCodePaths[splitIndex];
-        final File apkFile = new File(apkPath);
 
         mParseError = PackageManager.INSTALL_SUCCEEDED;
         mArchiveSourcePath = apkPath;
@@ -1054,33 +1058,38 @@
 
     /**
      * Collect certificates from all the APKs described in the given package,
-     * populating {@link Package#mSignatures}. This also asserts that all APK
+     * populating {@link Package#mSignatures}.
+     * <p>Depending upon the parser flags, this may also asserts that all APK
      * contents are signed correctly and consistently.
      */
-    public void collectCertificates(Package pkg, int flags) throws PackageParserException {
+    public void collectCertificates(Package pkg, int parseFlags) throws PackageParserException {
         pkg.mCertificates = null;
         pkg.mSignatures = null;
         pkg.mSigningKeys = null;
 
-        collectCertificates(pkg, new File(pkg.baseCodePath), flags);
+        collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags);
 
         if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
             for (String splitCodePath : pkg.splitCodePaths) {
-                collectCertificates(pkg, new File(splitCodePath), flags);
+                collectCertificates(pkg, new File(splitCodePath), parseFlags);
             }
         }
     }
 
-    private static void collectCertificates(Package pkg, File apkFile, int flags)
+    private static void collectCertificates(Package pkg, File apkFile, int parseFlags)
             throws PackageParserException {
-        final boolean requireCode = ((flags & PARSE_ENFORCE_CODE) != 0)
+        final boolean requireCode = ((parseFlags & PARSE_ENFORCE_CODE) != 0)
                 && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0);
         final String apkPath = apkFile.getAbsolutePath();
+        final boolean skipVerification = Build.IS_DEBUGGABLE
+                && ((parseFlags & PARSE_SKIP_VERIFICATION) != 0);
 
-        boolean codeFound = false;
+        boolean codeFound = skipVerification;
         StrictJarFile jarFile = null;
         try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
             jarFile = new StrictJarFile(apkPath);
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
             // Always verify manifest, regardless of source
             final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
@@ -1089,11 +1098,12 @@
                         "Package " + apkPath + " has no manifest");
             }
 
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "buildVerifyList");
             final List<ZipEntry> toVerify = new ArrayList<>();
             toVerify.add(manifestEntry);
 
             // If we're parsing an untrusted package, verify all contents
-            if ((flags & PARSE_IS_SYSTEM) == 0) {
+            if (!skipVerification && (parseFlags & PARSE_IS_SYSTEM) == 0) {
                 final Iterator<ZipEntry> i = jarFile.iterator();
                 while (i.hasNext()) {
                     final ZipEntry entry = i.next();
@@ -1110,6 +1120,7 @@
                     toVerify.add(entry);
                 }
             }
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
             if (!codeFound && requireCode) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -1119,6 +1130,7 @@
             // Verify that entries are signed consistently with the first entry
             // we encountered. Note that for splits, certificates may have
             // already been populated during an earlier parse of a base APK.
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyEntries");
             for (ZipEntry entry : toVerify) {
                 final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
                 if (ArrayUtils.isEmpty(entryCerts)) {
@@ -1135,6 +1147,9 @@
                     for (int i=0; i < entryCerts.length; i++) {
                         pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
                     }
+                    if (skipVerification) {
+                        break;
+                    }
                 } else {
                     if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) {
                         throw new PackageParserException(
@@ -1144,6 +1159,7 @@
                     }
                 }
             }
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         } catch (GeneralSecurityException e) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                     "Failed to collect certificates from " + apkPath, e);
@@ -1199,7 +1215,8 @@
             if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
                 // TODO: factor signature related items out of Package object
                 final Package tempPkg = new Package(null);
-                collectCertificates(tempPkg, apkFile, 0);
+                // TODO: fix b/25118622; pass in '0' for parse flags
+                collectCertificates(tempPkg, apkFile, flags & PARSE_SKIP_VERIFICATION);
                 signatures = tempPkg.mSignatures;
             } else {
                 signatures = null;
@@ -1456,7 +1473,7 @@
         int supportsXLargeScreens = 1;
         int resizeable = 1;
         int anyDensity = 1;
-        
+
         int outerDepth = parser.getDepth();
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -1612,7 +1629,7 @@
                     String minCode = null;
                     int targetVers = 0;
                     String targetCode = null;
-                    
+
                     TypedValue val = sa.peekValue(
                             com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion);
                     if (val != null) {
@@ -1623,7 +1640,7 @@
                             targetVers = minVers = val.data;
                         }
                     }
-                    
+
                     val = sa.peekValue(
                             com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
                     if (val != null) {
@@ -1634,7 +1651,7 @@
                             targetVers = val.data;
                         }
                     }
-                    
+
                     sa.recycle();
 
                     if (minCode != null) {
@@ -1663,7 +1680,7 @@
                         mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
                         return null;
                     }
-                    
+
                     if (targetCode != null) {
                         boolean allowedCodename = false;
                         for (String codename : SDK_CODENAMES) {
@@ -1730,9 +1747,9 @@
                         anyDensity);
 
                 sa.recycle();
-                
+
                 XmlUtils.skipCurrentTag(parser);
-                
+
             } else if (tagName.equals("protected-broadcast")) {
                 sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);
@@ -1754,12 +1771,12 @@
                 }
 
                 XmlUtils.skipCurrentTag(parser);
-                
+
             } else if (tagName.equals("instrumentation")) {
                 if (parseInstrumentation(pkg, res, parser, attrs, outError) == null) {
                     return null;
                 }
-                
+
             } else if (tagName.equals("original-package")) {
                 sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.AndroidManifestOriginalPackage);
@@ -1777,7 +1794,7 @@
                 sa.recycle();
 
                 XmlUtils.skipCurrentTag(parser);
-                
+
             } else if (tagName.equals("adopt-permissions")) {
                 sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.AndroidManifestOriginalPackage);
@@ -1795,12 +1812,12 @@
                 }
 
                 XmlUtils.skipCurrentTag(parser);
-                
+
             } else if (tagName.equals("uses-gl-texture")) {
                 // Just skip this tag
                 XmlUtils.skipCurrentTag(parser);
                 continue;
-                
+
             } else if (tagName.equals("compatible-screens")) {
                 // Just skip this tag
                 XmlUtils.skipCurrentTag(parser);
@@ -1808,12 +1825,12 @@
             } else if (tagName.equals("supports-input")) {
                 XmlUtils.skipCurrentTag(parser);
                 continue;
-                
+
             } else if (tagName.equals("eat-comment")) {
                 // Just skip this tag
                 XmlUtils.skipCurrentTag(parser);
                 continue;
-                
+
             } else if (RIGID_PARSER) {
                 outError[0] = "Bad element under <manifest>: "
                     + parser.getName();
@@ -1906,8 +1923,7 @@
         return pkg;
     }
 
-    private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs)
-            throws XmlPullParserException, IOException {
+    private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs) {
         FeatureInfo fi = new FeatureInfo();
         TypedArray sa = res.obtainAttributes(attrs,
                 com.android.internal.R.styleable.AndroidManifestUsesFeature);
@@ -2017,7 +2033,7 @@
         }
         return proc.intern();
     }
-    
+
     private static String buildProcessName(String pkg, String defProc,
             CharSequence procSeq, int flags, String[] separateProcesses,
             String[] outError) {
@@ -2226,7 +2242,7 @@
         }
 
         sa.recycle();
-        
+
         if (!parseAllMetaData(res, parser, attrs, "<permission-group>", perm,
                 outError)) {
             mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
@@ -2265,7 +2281,7 @@
         if (perm.info.group != null) {
             perm.info.group = perm.info.group.intern();
         }
-        
+
         perm.info.descriptionRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestPermission_description,
                 0);
@@ -2296,7 +2312,7 @@
                 return null;
             }
         }
-        
+
         if (!parseAllMetaData(res, parser, attrs, "<permission>", perm,
                 outError)) {
             mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
@@ -2329,7 +2345,7 @@
         }
 
         sa.recycle();
-        
+
         int index = perm.info.name.indexOf('.');
         if (index > 0) {
             index = perm.info.name.indexOf('.', index+1);
@@ -2371,9 +2387,9 @@
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_banner);
             mParseInstrumentationArgs.tag = "<instrumentation>";
         }
-        
+
         mParseInstrumentationArgs.sa = sa;
-        
+
         Instrumentation a = new Instrumentation(mParseInstrumentationArgs,
                 new InstrumentationInfo());
         if (outError[0] != null) {
@@ -2650,10 +2666,10 @@
             }
             ai.processName = buildProcessName(ai.packageName, null, pname,
                     flags, mSeparateProcesses, outError);
-    
+
             ai.enabled = sa.getBoolean(
                     com.android.internal.R.styleable.AndroidManifestApplication_enabled, true);
-            
+
             if (sa.getBoolean(
                     com.android.internal.R.styleable.AndroidManifestApplication_isGame, false)) {
                 ai.flags |= ApplicationInfo.FLAG_IS_GAME;
@@ -3022,7 +3038,7 @@
             outInfo.icon = iconVal;
             outInfo.nonLocalizedLabel = null;
         }
-        
+
         int logoVal = sa.getResourceId(logoRes, 0);
         if (logoVal != 0) {
             outInfo.logo = logoVal;
@@ -3061,11 +3077,11 @@
                     R.styleable.AndroidManifestActivity_description,
                     R.styleable.AndroidManifestActivity_enabled);
         }
-        
+
         mParseActivityArgs.tag = receiver ? "<receiver>" : "<activity>";
         mParseActivityArgs.sa = sa;
         mParseActivityArgs.flags = flags;
-        
+
         Activity a = new Activity(mParseActivityArgs, new ActivityInfo());
         if (outError[0] != null) {
             sa.recycle();
@@ -3236,7 +3252,7 @@
                 outError[0] = "Heavy-weight applications can not have receivers in main process";
             }
         }
-        
+
         if (outError[0] != null) {
             return null;
         }
@@ -3282,8 +3298,8 @@
                         outError)) == null) {
                     return null;
                 }
-            } else if (!receiver && parser.getName().equals("initial-layout")) {
-                parseInitialLayout(res, attrs, a);
+            } else if (!receiver && parser.getName().equals("layout")) {
+                parseLayout(res, attrs, a);
             } else {
                 if (!RIGID_PARSER) {
                     Slog.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
@@ -3316,41 +3332,44 @@
         return a;
     }
 
-    private void parseInitialLayout(Resources res, AttributeSet attrs, Activity a) {
+    private void parseLayout(Resources res, AttributeSet attrs, Activity a) {
         TypedArray sw = res.obtainAttributes(attrs,
-                com.android.internal.R.styleable.AndroidManifestInitialLayout);
+                com.android.internal.R.styleable.AndroidManifestLayout);
         int width = -1;
         float widthFraction = -1f;
         int height = -1;
         float heightFraction = -1f;
         final int widthType = sw.getType(
-                com.android.internal.R.styleable.AndroidManifestInitialLayout_activityWidth);
+                com.android.internal.R.styleable.AndroidManifestLayout_initialWidth);
         if (widthType == TypedValue.TYPE_FRACTION) {
             widthFraction = sw.getFraction(
-                    com.android.internal.R.styleable.AndroidManifestInitialLayout_activityWidth,
+                    com.android.internal.R.styleable.AndroidManifestLayout_initialWidth,
                     1, 1, -1);
         } else if (widthType == TypedValue.TYPE_DIMENSION) {
             width = sw.getDimensionPixelSize(
-                    com.android.internal.R.styleable.AndroidManifestInitialLayout_activityWidth,
+                    com.android.internal.R.styleable.AndroidManifestLayout_initialWidth,
                     -1);
         }
         final int heightType = sw.getType(
-                com.android.internal.R.styleable.AndroidManifestInitialLayout_activityHeight);
+                com.android.internal.R.styleable.AndroidManifestLayout_initialHeight);
         if (heightType == TypedValue.TYPE_FRACTION) {
             heightFraction = sw.getFraction(
-                    com.android.internal.R.styleable.AndroidManifestInitialLayout_activityHeight,
+                    com.android.internal.R.styleable.AndroidManifestLayout_initialHeight,
                     1, 1, -1);
         } else if (heightType == TypedValue.TYPE_DIMENSION) {
             height = sw.getDimensionPixelSize(
-                    com.android.internal.R.styleable.AndroidManifestInitialLayout_activityHeight,
+                    com.android.internal.R.styleable.AndroidManifestLayout_initialHeight,
                     -1);
         }
         int gravity = sw.getInt(
-                com.android.internal.R.styleable.AndroidManifestInitialLayout_gravity,
+                com.android.internal.R.styleable.AndroidManifestLayout_gravity,
                 Gravity.CENTER);
+        int minimalSize = sw.getDimensionPixelSize(
+                com.android.internal.R.styleable.AndroidManifestLayout_minimalSize,
+                -1);
         sw.recycle();
-        a.info.initialLayout = new ActivityInfo.InitialLayout(width, widthFraction,
-                height, heightFraction, gravity);
+        a.info.layout = new ActivityInfo.Layout(width, widthFraction,
+                height, heightFraction, gravity, minimalSize);
     }
 
     private Activity parseActivityAlias(Package owner, Resources res,
@@ -3388,10 +3407,10 @@
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled);
             mParseActivityAliasArgs.tag = "<activity-alias>";
         }
-        
+
         mParseActivityAliasArgs.sa = sa;
         mParseActivityAliasArgs.flags = flags;
-        
+
         Activity target = null;
 
         final int NA = owner.activities.size();
@@ -3432,7 +3451,7 @@
         info.uiOptions = target.info.uiOptions;
         info.parentActivityName = target.info.parentActivityName;
         info.maxRecents = target.info.maxRecents;
-        info.initialLayout = target.info.initialLayout;
+        info.layout = target.info.layout;
 
         Activity a = new Activity(mParseActivityAliasArgs, info);
         if (outError[0] != null) {
@@ -3540,10 +3559,10 @@
                     com.android.internal.R.styleable.AndroidManifestProvider_enabled);
             mParseProviderArgs.tag = "<provider>";
         }
-        
+
         mParseProviderArgs.sa = sa;
         mParseProviderArgs.flags = flags;
-        
+
         Provider p = new Provider(mParseProviderArgs, new ProviderInfo());
         if (outError[0] != null) {
             sa.recycle();
@@ -3632,7 +3651,7 @@
                 return null;
             }
         }
-        
+
         if (cpname == null) {
             outError[0] = "<provider> does not include authorities attribute";
             return null;
@@ -3675,7 +3694,7 @@
                         outInfo.metaData, outError)) == null) {
                     return false;
                 }
-                
+
             } else if (parser.getName().equals("grant-uri-permission")) {
                 TypedArray sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.AndroidManifestGrantUriPermission);
@@ -3699,7 +3718,7 @@
                 if (str != null) {
                     pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
                 }
-                
+
                 sa.recycle();
 
                 if (pa != null) {
@@ -3746,7 +3765,7 @@
                 if (writePermission == null) {
                     writePermission = permission;
                 }
-                
+
                 boolean havePerm = false;
                 if (readPermission != null) {
                     readPermission = readPermission.intern();
@@ -3769,7 +3788,7 @@
                         return false;
                     }
                 }
-                
+
                 String path = sa.getNonConfigurationString(
                         com.android.internal.R.styleable.AndroidManifestPathPermission_path, 0);
                 if (path != null) {
@@ -3852,10 +3871,10 @@
                     com.android.internal.R.styleable.AndroidManifestService_enabled);
             mParseServiceArgs.tag = "<service>";
         }
-        
+
         mParseServiceArgs.sa = sa;
         mParseServiceArgs.flags = flags;
-        
+
         Service s = new Service(mParseServiceArgs, new ServiceInfo());
         if (outError[0] != null) {
             sa.recycle();
@@ -3912,7 +3931,7 @@
                 return null;
             }
         }
-        
+
         int outerDepth = parser.getDepth();
         int type;
         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -3957,7 +3976,7 @@
 
     private boolean parseAllMetaData(Resources res,
             XmlPullParser parser, AttributeSet attrs, String tag,
-            Component outInfo, String[] outError)
+            Component<?> outInfo, String[] outError)
             throws XmlPullParserException, IOException {
         int outerDepth = parser.getDepth();
         int type;
@@ -4010,7 +4029,7 @@
         }
 
         name = name.intern();
-        
+
         TypedValue v = sa.peekValue(
                 com.android.internal.R.styleable.AndroidManifestMetaData_resource);
         if (v != null && v.resourceId != 0) {
@@ -4152,7 +4171,7 @@
 
         outInfo.icon = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
-        
+
         outInfo.logo = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0);
 
@@ -4373,7 +4392,7 @@
         public ArrayList<String> mOriginalPackages = null;
         public String mRealPackage = null;
         public ArrayList<String> mAdoptPermissions = null;
-        
+
         // We store the application meta-data independently to avoid multiple unwanted references
         public Bundle mAppMetaData = null;
 
@@ -4382,7 +4401,7 @@
 
         // The version name declared for this package.
         public String mVersionName;
-        
+
         // The shared user id that this package wants to use.
         public String mSharedUserId;
 
@@ -4604,7 +4623,7 @@
 
         ComponentName componentName;
         String componentShortName;
-        
+
         public Component(Package _owner) {
             owner = _owner;
             intents = null;
@@ -4636,7 +4655,7 @@
                 outInfo.icon = iconVal;
                 outInfo.nonLocalizedLabel = null;
             }
-            
+
             int logoVal = args.sa.getResourceId(args.logoRes, 0);
             if (logoVal != 0) {
                 outInfo.logo = logoVal;
@@ -4676,11 +4695,11 @@
                         owner.applicationInfo.processName, pname,
                         args.flags, args.sepProcesses, args.outError);
             }
-            
+
             if (args.descriptionRes != 0) {
                 outInfo.descriptionRes = args.sa.getResourceId(args.descriptionRes, 0);
             }
-            
+
             outInfo.enabled = args.sa.getBoolean(args.enabledRes, true);
         }
 
@@ -4691,7 +4710,7 @@
             componentName = clone.componentName;
             componentShortName = clone.componentShortName;
         }
-        
+
         public ComponentName getComponentName() {
             if (componentName != null) {
                 return componentName;
@@ -4716,7 +4735,7 @@
             componentShortName = null;
         }
     }
-    
+
     public final static class Permission extends Component<IntentInfo> {
         public final PermissionInfo info;
         public boolean tree;
@@ -4731,7 +4750,7 @@
             super(_owner);
             info = _info;
         }
-        
+
         public void setPackageName(String packageName) {
             super.setPackageName(packageName);
             info.packageName = packageName;
@@ -4922,7 +4941,7 @@
             info = _info;
             info.applicationInfo = args.owner.applicationInfo;
         }
-        
+
         public void setPackageName(String packageName) {
             super.setPackageName(packageName);
             info.packageName = packageName;
@@ -4976,7 +4995,7 @@
             info = _info;
             info.applicationInfo = args.owner.applicationInfo;
         }
-        
+
         public void setPackageName(String packageName) {
             super.setPackageName(packageName);
             info.packageName = packageName;
@@ -5019,7 +5038,7 @@
             info.applicationInfo = args.owner.applicationInfo;
             syncable = false;
         }
-        
+
         public Provider(Provider existingProvider) {
             super(existingProvider);
             this.info = existingProvider.info;
@@ -5070,7 +5089,7 @@
             super(args, _info);
             info = _info;
         }
-        
+
         public void setPackageName(String packageName) {
             super.setPackageName(packageName);
             info.packageName = packageName;
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index be48633..5539dc9 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -16,7 +16,6 @@
 
 package android.ddm;
 
-import android.opengl.GLUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewDebug;
@@ -41,9 +40,6 @@
  * Support for these features are advertised via {@link DdmHandleHello}.
  */
 public class DdmHandleViewDebug extends ChunkHandler {
-    /** Enable/Disable tracing of OpenGL calls. */
-    public static final int CHUNK_VUGL = type("VUGL");
-
     /** List {@link ViewRootImpl}'s of this process. */
     private static final int CHUNK_VULW = type("VULW");
 
@@ -97,7 +93,6 @@
     private DdmHandleViewDebug() {}
 
     public static void register() {
-        DdmServer.registerHandler(CHUNK_VUGL, sInstance);
         DdmServer.registerHandler(CHUNK_VULW, sInstance);
         DdmServer.registerHandler(CHUNK_VURT, sInstance);
         DdmServer.registerHandler(CHUNK_VUOP, sInstance);
@@ -115,9 +110,7 @@
     public Chunk handleChunk(Chunk request) {
         int type = request.type;
 
-        if (type == CHUNK_VUGL) {
-            return handleOpenGlTrace(request);
-        } else if (type == CHUNK_VULW) {
+        if (type == CHUNK_VULW) {
             return listWindows();
         }
 
@@ -165,12 +158,6 @@
         }
     }
 
-    private Chunk handleOpenGlTrace(Chunk request) {
-        ByteBuffer in = wrapChunk(request);
-        GLUtils.setTracingLevel(in.getInt());
-        return null;    // empty response
-    }
-
     /** Returns the list of windows owned by this client. */
     private Chunk listWindows() {
         String[] windowNames = WindowManagerGlobal.getInstance().getViewRootNames();
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 30cdfd3..2fe8fb6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -78,14 +78,14 @@
                 sSensorModuleInitialized = true;
                 nativeClassInit();
             }
-        }
 
-        // initialize the sensor list
-        for (int index = 0;;++index) {
-            Sensor sensor = new Sensor();
-            if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
-            mFullSensorsList.add(sensor);
-            mHandleToSensor.append(sensor.getHandle(), sensor);
+            // initialize the sensor list
+            for (int index = 0;;++index) {
+                Sensor sensor = new Sensor();
+                if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+                mFullSensorsList.add(sensor);
+                mHandleToSensor.append(sensor.getHandle(), sensor);
+            }
         }
     }
 
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index c8b45c7..174291e 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -61,10 +61,14 @@
     // Registers an input devices changed listener.
     void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
 
+    // Queries whether the device is currently in tablet mode
+    int isInTabletMode();
     // Registers a tablet mode change listener
     void registerTabletModeChangedListener(ITabletModeChangedListener listener);
 
     // Input device vibrator control.
     void vibrate(int deviceId, in long[] pattern, int repeat, IBinder token);
     void cancelVibrate(int deviceId, IBinder token);
+
+    void setPointerIconShape(int shapeId);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index bae5757..201afee 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,6 +19,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 
+import android.annotation.IntDef;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
@@ -39,6 +40,8 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -105,13 +108,14 @@
      * of a key character map for a particular keyboard layout.  The label on the receiver
      * is used to name the collection of keyboard layouts provided by this receiver in the
      * keyboard layout settings.
-     * <pre></code>
+     * <pre><code>
      * &lt;?xml version="1.0" encoding="utf-8"?>
      * &lt;keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
      *     &lt;keyboard-layout android:name="keyboard_layout_english_us"
      *             android:label="@string/keyboard_layout_english_us_label"
      *             android:keyboardLayout="@raw/keyboard_layout_english_us" />
      * &lt;/keyboard-layouts>
+     * </pre></code>
      * </p><p>
      * The <code>android:name</code> attribute specifies an identifier by which
      * the keyboard layout will be known in the package.
@@ -179,6 +183,31 @@
      */
     public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;  // see InputDispatcher.h
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SWITCH_STATE_UNKNOWN, SWITCH_STATE_OFF, SWITCH_STATE_ON})
+    public @interface SwitchState {}
+
+    /**
+     * Switch State: Unknown.
+     *
+     * The system has yet to report a valid value for the switch.
+     * @hide
+     */
+    public static final int SWITCH_STATE_UNKNOWN = -1;
+
+    /**
+     * Switch State: Off.
+     * @hide
+     */
+    public static final int SWITCH_STATE_OFF = 0;
+
+    /**
+     * Switch State: On.
+     * @hide
+     */
+    public static final int SWITCH_STATE_ON = 1;
+
     private InputManager(IInputManager im) {
         mIm = im;
     }
@@ -340,6 +369,23 @@
     }
 
     /**
+     * Queries whether the device is in tablet mode.
+     *
+     * @return The tablet switch state which is one of {@link #SWITCH_STATE_UNKNOWN},
+     * {@link #SWITCH_STATE_OFF} or {@link #SWITCH_STATE_ON}.
+     * @hide
+     */
+    @SwitchState
+    public int isInTabletMode() {
+        try {
+            return mIm.isInTabletMode();
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not get tablet mode state", ex);
+            return SWITCH_STATE_UNKNOWN;
+        }
+    }
+
+    /**
      * Register a tablet mode changed listener.
      *
      * @param listener The listener to register.
@@ -757,6 +803,22 @@
         }
     }
 
+    /**
+     * Changes the mouse pointer's icon shape into the specified id.
+     *
+     * @param iconId The id of the pointer graphic, as a value between
+     * {@link PointerIcon.STYLE_ARROW} and {@link PointerIcon.STYLE_GRABBING}.
+     *
+     * @hide
+     */
+    public void setPointerIconShape(int iconId) {
+        try {
+            mIm.setPointerIconShape(iconId);
+        } catch (RemoteException ex) {
+            // Do nothing.
+        }
+    }
+
     private void populateInputDevicesLocked() {
         if (mInputDevicesChangedListener == null) {
             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index b777e8c..d1000ad 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1,12 +1,12 @@
 /*
  * Copyright (C) 2008-2009 Google Inc.
- * 
+ *
  * 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
@@ -57,7 +57,7 @@
 /**
  * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
  * detecting key presses and touch movements.
- * 
+ *
  * @attr ref android.R.styleable#KeyboardView_keyBackground
  * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
  * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
@@ -73,7 +73,7 @@
      * Listener for virtual keyboard events.
      */
     public interface OnKeyboardActionListener {
-        
+
         /**
          * Called when the user presses a key. This is sent before the {@link #onKey} is called.
          * For keys that repeat, this is only called once.
@@ -81,7 +81,7 @@
          * key, the value will be zero.
          */
         void onPress(int primaryCode);
-        
+
         /**
          * Called when the user releases a key. This is sent after the {@link #onKey} is called.
          * For keys that repeat, this is only called once.
@@ -106,22 +106,22 @@
          * @param text the sequence of characters to be displayed.
          */
         void onText(CharSequence text);
-        
+
         /**
          * Called when the user quickly moves the finger from right to left.
          */
         void swipeLeft();
-        
+
         /**
          * Called when the user quickly moves the finger from left to right.
          */
         void swipeRight();
-        
+
         /**
          * Called when the user quickly moves the finger from up to down.
          */
         void swipeDown();
-        
+
         /**
          * Called when the user quickly moves the finger from down to up.
          */
@@ -131,8 +131,8 @@
     private static final boolean DEBUG = false;
     private static final int NOT_A_KEY = -1;
     private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
-    private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };   
-    
+    private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
+
     private Keyboard mKeyboard;
     private int mCurrentKeyIndex = NOT_A_KEY;
     private int mLabelTextSize;
@@ -141,7 +141,7 @@
     private float mShadowRadius;
     private int mShadowColor;
     private float mBackgroundDimAmount;
-    
+
     private TextView mPreviewText;
     private PopupWindow mPreviewPopup;
     private int mPreviewTextSizeLarge;
@@ -162,7 +162,7 @@
 
     /** Listener for {@link OnKeyboardActionListener}. */
     private OnKeyboardActionListener mKeyboardActionListener;
-    
+
     private static final int MSG_SHOW_PREVIEW = 1;
     private static final int MSG_REMOVE_PREVIEW = 2;
     private static final int MSG_REPEAT = 3;
@@ -171,7 +171,7 @@
     private static final int DELAY_BEFORE_PREVIEW = 0;
     private static final int DELAY_AFTER_PREVIEW = 70;
     private static final int DEBOUNCE_TIME = 70;
-    
+
     private int mVerticalCorrection;
     private int mProximityThreshold;
 
@@ -187,10 +187,10 @@
     private int mStartY;
 
     private boolean mProximityCorrectOn;
-    
+
     private Paint mPaint;
     private Rect mPadding;
-    
+
     private long mDownTime;
     private long mLastMoveTime;
     private int mLastKey;
@@ -253,28 +253,7 @@
     /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
     private boolean mHeadsetRequiredToHearPasswordsAnnounced;
 
-    Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SHOW_PREVIEW:
-                    showKey(msg.arg1);
-                    break;
-                case MSG_REMOVE_PREVIEW:
-                    mPreviewText.setVisibility(INVISIBLE);
-                    break;
-                case MSG_REPEAT:
-                    if (repeatKey()) {
-                        Message repeat = Message.obtain(this, MSG_REPEAT);
-                        sendMessageDelayed(repeat, REPEAT_INTERVAL);                        
-                    }
-                    break;
-                case MSG_LONGPRESS:
-                    openPopupIfRequired((MotionEvent) msg.obj);
-                    break;
-            }
-        }
-    };
+    Handler mHandler;
 
     public KeyboardView(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
@@ -298,7 +277,7 @@
         int keyTextSize = 0;
 
         int n = a.getIndexCount();
-        
+
         for (int i = 0; i < n; i++) {
             int attr = a.getIndex(i);
 
@@ -338,7 +317,7 @@
                 break;
             }
         }
-        
+
         a = mContext.obtainStyledAttributes(
                 com.android.internal.R.styleable.Theme);
         mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
@@ -352,16 +331,16 @@
         } else {
             mShowPreview = false;
         }
-        
+
         mPreviewPopup.setTouchable(false);
-        
+
         mPopupKeyboard = new PopupWindow(context);
         mPopupKeyboard.setBackgroundDrawable(null);
         //mPopupKeyboard.setClippingEnabled(false);
-        
+
         mPopupParent = this;
         //mPredicting = true;
-        
+
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
         mPaint.setTextSize(keyTextSize);
@@ -380,64 +359,94 @@
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
         resetMultiTap();
-        initGestureDetector();
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        initGestureDetector();
+        if (mHandler == null) {
+            mHandler = new Handler() {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case MSG_SHOW_PREVIEW:
+                            showKey(msg.arg1);
+                            break;
+                        case MSG_REMOVE_PREVIEW:
+                            mPreviewText.setVisibility(INVISIBLE);
+                            break;
+                        case MSG_REPEAT:
+                            if (repeatKey()) {
+                                Message repeat = Message.obtain(this, MSG_REPEAT);
+                                sendMessageDelayed(repeat, REPEAT_INTERVAL);
+                            }
+                            break;
+                        case MSG_LONGPRESS:
+                            openPopupIfRequired((MotionEvent) msg.obj);
+                            break;
+                    }
+                }
+            };
+        }
+    }
 
     private void initGestureDetector() {
-        mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
-            @Override
-            public boolean onFling(MotionEvent me1, MotionEvent me2, 
-                    float velocityX, float velocityY) {
-                if (mPossiblePoly) return false;
-                final float absX = Math.abs(velocityX);
-                final float absY = Math.abs(velocityY);
-                float deltaX = me2.getX() - me1.getX();
-                float deltaY = me2.getY() - me1.getY();
-                int travelX = getWidth() / 2; // Half the keyboard width
-                int travelY = getHeight() / 2; // Half the keyboard height
-                mSwipeTracker.computeCurrentVelocity(1000);
-                final float endingVelocityX = mSwipeTracker.getXVelocity();
-                final float endingVelocityY = mSwipeTracker.getYVelocity();
-                boolean sendDownKey = false;
-                if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
-                    if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
-                        sendDownKey = true;
-                    } else {
-                        swipeRight();
-                        return true;
+        if (mGestureDetector == null) {
+            mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
+                @Override
+                public boolean onFling(MotionEvent me1, MotionEvent me2,
+                        float velocityX, float velocityY) {
+                    if (mPossiblePoly) return false;
+                    final float absX = Math.abs(velocityX);
+                    final float absY = Math.abs(velocityY);
+                    float deltaX = me2.getX() - me1.getX();
+                    float deltaY = me2.getY() - me1.getY();
+                    int travelX = getWidth() / 2; // Half the keyboard width
+                    int travelY = getHeight() / 2; // Half the keyboard height
+                    mSwipeTracker.computeCurrentVelocity(1000);
+                    final float endingVelocityX = mSwipeTracker.getXVelocity();
+                    final float endingVelocityY = mSwipeTracker.getYVelocity();
+                    boolean sendDownKey = false;
+                    if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
+                        if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeRight();
+                            return true;
+                        }
+                    } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
+                        if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeLeft();
+                            return true;
+                        }
+                    } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
+                        if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeUp();
+                            return true;
+                        }
+                    } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
+                        if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeDown();
+                            return true;
+                        }
                     }
-                } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
-                    if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
-                        sendDownKey = true;
-                    } else {
-                        swipeLeft();
-                        return true;
-                    }
-                } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
-                    if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
-                        sendDownKey = true;
-                    } else {
-                        swipeUp();
-                        return true;
-                    }
-                } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
-                    if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
-                        sendDownKey = true;
-                    } else {
-                        swipeDown();
-                        return true;
-                    }
-                }
 
-                if (sendDownKey) {
-                    detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
+                    if (sendDownKey) {
+                        detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
+                    }
+                    return false;
                 }
-                return false;
-            }
-        });
+            });
 
-        mGestureDetector.setIsLongpressEnabled(false);
+            mGestureDetector.setIsLongpressEnabled(false);
+        }
     }
 
     public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
@@ -487,7 +496,7 @@
     public Keyboard getKeyboard() {
         return mKeyboard;
     }
-    
+
     /**
      * Sets the state of the shift key of the keyboard, if any.
      * @param shifted whether or not to enable the state of the shift key
@@ -520,7 +529,7 @@
 
     /**
      * Enables or disables the key feedback popup. This is a popup that shows a magnified
-     * version of the depressed key. By default the preview is enabled. 
+     * version of the depressed key. By default the preview is enabled.
      * @param previewEnabled whether or not to enable the key feedback popup
      * @see #isPreviewEnabled()
      */
@@ -536,14 +545,14 @@
     public boolean isPreviewEnabled() {
         return mShowPreview;
     }
-    
+
     public void setVerticalCorrection(int verticalOffset) {
-        
+
     }
     public void setPopupParent(View v) {
         mPopupParent = v;
     }
-    
+
     public void setPopupOffset(int x, int y) {
         mMiniKeyboardOffsetX = x;
         mMiniKeyboardOffsetY = y;
@@ -569,9 +578,9 @@
         return mProximityCorrectOn;
     }
 
-    /** 
+    /**
      * Popup keyboard close button clicked.
-     * @hide 
+     * @hide
      */
     public void onClick(View v) {
         dismissPopupKeyboard();
@@ -654,9 +663,9 @@
         }
         final Canvas canvas = mCanvas;
         canvas.clipRect(mDirtyRect, Op.REPLACE);
-        
+
         if (mKeyboard == null) return;
-        
+
         final Paint paint = mPaint;
         final Drawable keyBackground = mKeyBackground;
         final Rect clipRegion = mClipRegion;
@@ -689,15 +698,15 @@
 
             // Switch the character to uppercase if shift is pressed
             String label = key.label == null? null : adjustCase(key.label).toString();
-            
+
             final Rect bounds = keyBackground.getBounds();
-            if (key.width != bounds.right || 
+            if (key.width != bounds.right ||
                     key.height != bounds.bottom) {
                 keyBackground.setBounds(0, 0, key.width, key.height);
             }
             canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
             keyBackground.draw(canvas);
-            
+
             if (label != null) {
                 // For characters, use large font. For labels like "Done", use small font.
                 if (label.length() > 1 && key.codes.length < 2) {
@@ -719,12 +728,12 @@
                 // Turn off drop shadow
                 paint.setShadowLayer(0, 0, 0, 0);
             } else if (key.icon != null) {
-                final int drawableX = (key.width - padding.left - padding.right 
+                final int drawableX = (key.width - padding.left - padding.right
                                 - key.icon.getIntrinsicWidth()) / 2 + padding.left;
-                final int drawableY = (key.height - padding.top - padding.bottom 
+                final int drawableY = (key.height - padding.top - padding.bottom
                         - key.icon.getIntrinsicHeight()) / 2 + padding.top;
                 canvas.translate(drawableX, drawableY);
-                key.icon.setBounds(0, 0, 
+                key.icon.setBounds(0, 0,
                         key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
                 key.icon.draw(canvas);
                 canvas.translate(-drawableX, -drawableY);
@@ -748,7 +757,7 @@
             paint.setColor(0xFF00FF00);
             canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
         }
-        
+
         mDrawPending = false;
         mDirtyRect.setEmpty();
     }
@@ -769,8 +778,8 @@
                 primaryIndex = nearestKeyIndices[i];
             }
 
-            if (((mProximityCorrectOn 
-                    && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) 
+            if (((mProximityCorrectOn
+                    && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
                     || isInside)
                     && key.codes[0] > 32) {
                 // Find insertion point
@@ -779,9 +788,9 @@
                     closestKeyDist = dist;
                     closestKey = nearestKeyIndices[i];
                 }
-                
+
                 if (allKeys == null) continue;
-                
+
                 for (int j = 0; j < mDistances.length; j++) {
                     if (mDistances[j] > dist) {
                         // Make space for nCodes codes
@@ -846,11 +855,11 @@
             return adjustCase(key.label);
         }
     }
-    
+
     private void showPreview(int keyIndex) {
         int oldKeyIndex = mCurrentKeyIndex;
         final PopupWindow previewPopup = mPreviewPopup;
-        
+
         mCurrentKeyIndex = keyIndex;
         // Release the old key and press the new key
         final Key[] keys = mKeys;
@@ -884,7 +893,7 @@
             if (previewPopup.isShowing()) {
                 if (keyIndex == NOT_A_KEY) {
                     mHandler.sendMessageDelayed(mHandler
-                            .obtainMessage(MSG_REMOVE_PREVIEW), 
+                            .obtainMessage(MSG_REMOVE_PREVIEW),
                             DELAY_AFTER_PREVIEW);
                 }
             }
@@ -894,20 +903,20 @@
                     showKey(keyIndex);
                 } else {
                     mHandler.sendMessageDelayed(
-                            mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), 
+                            mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
                             DELAY_BEFORE_PREVIEW);
                 }
             }
         }
     }
-    
+
     private void showKey(final int keyIndex) {
         final PopupWindow previewPopup = mPreviewPopup;
         final Key[] keys = mKeys;
         if (keyIndex < 0 || keyIndex >= mKeys.length) return;
         Key key = keys[keyIndex];
         if (key.icon != null) {
-            mPreviewText.setCompoundDrawables(null, null, null, 
+            mPreviewText.setCompoundDrawables(null, null, null,
                     key.iconPreview != null ? key.iconPreview : key.icon);
             mPreviewText.setText(null);
         } else {
@@ -921,9 +930,9 @@
                 mPreviewText.setTypeface(Typeface.DEFAULT);
             }
         }
-        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 
+        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width 
+        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
                 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
         final int popupHeight = mPreviewHeight;
         LayoutParams lp = mPreviewText.getLayoutParams();
@@ -969,7 +978,7 @@
         } else {
             previewPopup.setWidth(popupWidth);
             previewPopup.setHeight(popupHeight);
-            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY, 
+            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
                     mPopupPreviewX, mPopupPreviewY);
         }
         mPreviewText.setVisibility(VISIBLE);
@@ -1030,7 +1039,7 @@
 
     /**
      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
-     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 
+     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
      * draws the cached buffer.
      * @see #invalidateKey(int)
      */
@@ -1054,10 +1063,10 @@
         }
         final Key key = mKeys[keyIndex];
         mInvalidatedKey = key;
-        mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop, 
+        mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,
                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
         onBufferDraw();
-        invalidate(key.x + mPaddingLeft, key.y + mPaddingTop, 
+        invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
     }
 
@@ -1070,7 +1079,7 @@
             return false;
         }
 
-        Key popupKey = mKeys[mCurrentKey];        
+        Key popupKey = mKeys[mCurrentKey];
         boolean result = onLongPress(popupKey);
         if (result) {
             mAbortKey = true;
@@ -1105,12 +1114,12 @@
                         mKeyboardActionListener.onKey(primaryCode, keyCodes);
                         dismissPopupKeyboard();
                     }
-                    
+
                     public void onText(CharSequence text) {
                         mKeyboardActionListener.onText(text);
                         dismissPopupKeyboard();
                     }
-                    
+
                     public void swipeLeft() { }
                     public void swipeRight() { }
                     public void swipeUp() { }
@@ -1125,7 +1134,7 @@
                 //mInputView.setSuggest(mSuggest);
                 Keyboard keyboard;
                 if (popupKey.popupCharacters != null) {
-                    keyboard = new Keyboard(getContext(), popupKeyboardId, 
+                    keyboard = new Keyboard(getContext(), popupKeyboardId,
                             popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
                 } else {
                     keyboard = new Keyboard(getContext(), popupKeyboardId);
@@ -1133,9 +1142,9 @@
                 mMiniKeyboard.setKeyboard(keyboard);
                 mMiniKeyboard.setPopupParent(this);
                 mMiniKeyboardContainer.measure(
-                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), 
+                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
                         MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
-                
+
                 mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
             } else {
                 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
@@ -1184,7 +1193,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent me) {
-        // Convert multi-pointer up/down events to single up/down events to 
+        // Convert multi-pointer up/down events to single up/down events to
         // deal with the typical multi-pointer behavior of two-thumb typing
         final int pointerCount = me.getPointerCount();
         final int action = me.getAction();
@@ -1250,7 +1259,7 @@
             mHandler.removeMessages(MSG_LONGPRESS);
             return true;
         }
-        
+
         // Needs to be called after the gesture detector gets a turn, as it may have
         // displayed the mini keyboard
         if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
@@ -1272,7 +1281,7 @@
                 mDownTime = me.getEventTime();
                 mLastMoveTime = mDownTime;
                 checkMultiTap(eventTime, keyIndex);
-                mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? 
+                mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
                         mKeys[keyIndex].codes[0] : 0);
                 if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
                     mRepeatKeyIndex = mCurrentKey;
@@ -1371,11 +1380,11 @@
         detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
         return true;
     }
-    
+
     protected void swipeRight() {
         mKeyboardActionListener.swipeRight();
     }
-    
+
     protected void swipeLeft() {
         mKeyboardActionListener.swipeLeft();
     }
@@ -1393,7 +1402,7 @@
             mPreviewPopup.dismiss();
         }
         removeMessages();
-        
+
         dismissPopupKeyboard();
         mBuffer = null;
         mCanvas = null;
@@ -1401,9 +1410,11 @@
     }
 
     private void removeMessages() {
-        mHandler.removeMessages(MSG_REPEAT);
-        mHandler.removeMessages(MSG_LONGPRESS);
-        mHandler.removeMessages(MSG_SHOW_PREVIEW);
+        if (mHandler != null) {
+            mHandler.removeMessages(MSG_REPEAT);
+            mHandler.removeMessages(MSG_LONGPRESS);
+            mHandler.removeMessages(MSG_SHOW_PREVIEW);
+        }
     }
 
     @Override
@@ -1434,7 +1445,7 @@
         mLastTapTime = -1;
         mInMultiTap = false;
     }
-    
+
     private void checkMultiTap(long eventTime, int keyIndex) {
         if (keyIndex == NOT_A_KEY) return;
         Key key = mKeys[keyIndex];
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 444548f..ad9058f 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -901,8 +901,12 @@
      * Tells the underlying networking system that the caller wants to
      * begin using the named feature. The interpretation of {@code feature}
      * is completely up to each networking implementation.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @param networkType specifies which network the request pertains to
      * @param feature the name of the feature to be used
      * @return an integer value representing the outcome of the request.
@@ -952,8 +956,12 @@
      * Tells the underlying networking system that the caller is finished
      * using the named feature. The interpretation of {@code feature}
      * is completely up to each networking implementation.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @param networkType specifies which network the request pertains to
      * @param feature the name of the feature that is no longer needed
      * @return an integer value representing the outcome of the request.
@@ -1339,8 +1347,12 @@
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface. An attempt to add a route that
      * already exists is ignored, but treated as successful.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @param networkType the type of the network over which traffic to the specified
      * host is to be routed
      * @param hostAddress the IP address of the host to which the route is desired
@@ -1360,8 +1372,12 @@
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface. An attempt to add a route that
      * already exists is ignored, but treated as successful.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @param networkType the type of the network over which traffic to the specified
      * host is to be routed
      * @param hostAddress the IP address of the host to which the route is desired
@@ -1561,6 +1577,13 @@
         return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
+    /** {@hide} */
+    public static final void enforceChangePermission(Context context) {
+        int uid = Binder.getCallingUid();
+        Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
+                .getPackageNameForUid(context, uid), true /* throwException */);
+    }
+
     /** {@hide */
     public static final void enforceTetherChangePermission(Context context) {
         if (context.getResources().getStringArray(
@@ -1571,8 +1594,8 @@
                     android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService");
         } else {
             int uid = Binder.getCallingUid();
-            Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
-                    .getPackageNameForUid(context, uid), true);
+            Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
+                    .getPackageNameForUid(context, uid), true /* throwException */);
         }
     }
 
@@ -1677,8 +1700,11 @@
      * allowed between the tethered devices and this device, though upstream net
      * access will of course fail until an upstream network interface becomes
      * active.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param iface the interface name to tether.
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -1695,8 +1721,11 @@
 
     /**
      * Stop tethering the named interface.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param iface the interface name to untether.
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -1796,8 +1825,11 @@
      * attempt to switch to Rndis and subsequently tether the resulting
      * interface on {@code true} or turn off tethering and switch off
      * Rndis on {@code false}.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param enable a boolean - {@code true} to enable tethering
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -2467,8 +2499,11 @@
      * network may never attain, and whether a network will attain these states
      * is unknown prior to bringing up the network so the framework does not
      * know how to go about satisfing a request with these capabilities.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param request {@link NetworkRequest} describing this request.
      * @param networkCallback The {@link NetworkCallback} to be utilized for this
@@ -2490,8 +2525,12 @@
      * network is not found within the given time (in milliseconds) the
      * {@link NetworkCallback#unavailable} callback is called.  The request must
      * still be released normally by calling {@link releaseNetworkRequest}.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @param request {@link NetworkRequest} describing this request.
      * @param networkCallback The callbacks to be utilized for this request.  Note
      *                        the callbacks must not be shared - they uniquely specify
@@ -2564,8 +2603,12 @@
      * network may never attain, and whether a network will attain these states
      * is unknown prior to bringing up the network so the framework does not
      * know how to go about satisfing a request with these capabilities.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @param request {@link NetworkRequest} describing this request.
      * @param operation Action to perform when the network is available (corresponds
      *                  to the {@link NetworkCallback#onAvailable} call.  Typically
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 57eef83..b7a411e 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -288,7 +288,8 @@
         } else {
             final boolean matchesType = (sForceAllNetworkTypes
                     || contains(DATA_USAGE_NETWORK_TYPES, ident.mType));
-            return matchesType && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
+            return matchesType && !ArrayUtils.isEmpty(mMatchSubscriberIds)
+                    && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
         }
     }
 
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index 7673011..cf9243f 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -22,6 +22,7 @@
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
+import java.util.Arrays;
 
 /**
  * {@hide}
@@ -38,6 +39,7 @@
 public class SntpClient
 {
     private static final String TAG = "SntpClient";
+    private static final boolean DBG = true;
 
     private static final int REFERENCE_TIME_OFFSET = 16;
     private static final int ORIGINATE_TIME_OFFSET = 24;
@@ -47,8 +49,14 @@
 
     private static final int NTP_PORT = 123;
     private static final int NTP_MODE_CLIENT = 3;
+    private static final int NTP_MODE_SERVER = 4;
+    private static final int NTP_MODE_BROADCAST = 5;
     private static final int NTP_VERSION = 3;
 
+    private static final int NTP_LEAP_NOSYNC = 3;
+    private static final int NTP_STRATUM_DEATH = 0;
+    private static final int NTP_STRATUM_MAX = 15;
+
     // Number of seconds between Jan 1, 1900 and Jan 1, 1970
     // 70 years plus 17 leap days
     private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
@@ -62,6 +70,12 @@
     // round trip time in milliseconds
     private long mRoundTripTime;
 
+    private static class InvalidServerReplyException extends Exception {
+        public InvalidServerReplyException(String message) {
+            super(message);
+        }
+    }
+
     /**
      * Sends an SNTP request to the given host and processes the response.
      *
@@ -70,13 +84,23 @@
      * @return true if the transaction was successful.
      */
     public boolean requestTime(String host, int timeout) {
+        InetAddress address = null;
+        try {
+            address = InetAddress.getByName(host);
+        } catch (Exception e) {
+            if (DBG) Log.d(TAG, "request time failed: " + e);
+            return false;
+        }
+        return requestTime(address, NTP_PORT, timeout);
+    }
+
+    public boolean requestTime(InetAddress address, int port, int timeout) {
         DatagramSocket socket = null;
         try {
             socket = new DatagramSocket();
             socket.setSoTimeout(timeout);
-            InetAddress address = InetAddress.getByName(host);
             byte[] buffer = new byte[NTP_PACKET_SIZE];
-            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
+            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, port);
 
             // set mode = 3 (client) and version = 3
             // mode is in low 3 bits of first byte
@@ -84,8 +108,8 @@
             buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
 
             // get current time and write it to the request packet
-            long requestTime = System.currentTimeMillis();
-            long requestTicks = SystemClock.elapsedRealtime();
+            final long requestTime = System.currentTimeMillis();
+            final long requestTicks = SystemClock.elapsedRealtime();
             writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
 
             socket.send(request);
@@ -93,13 +117,21 @@
             // read the response
             DatagramPacket response = new DatagramPacket(buffer, buffer.length);
             socket.receive(response);
-            long responseTicks = SystemClock.elapsedRealtime();
-            long responseTime = requestTime + (responseTicks - requestTicks);
+            final long responseTicks = SystemClock.elapsedRealtime();
+            final long responseTime = requestTime + (responseTicks - requestTicks);
 
             // extract the results
-            long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
-            long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
-            long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
+            final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
+            final byte mode = (byte) (buffer[0] & 0x7);
+            final int stratum = (int) (buffer[1] & 0xff);
+            final long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
+            final long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
+            final long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
+
+            /* do sanity check according to RFC */
+            // TODO: validate originateTime == requestTime.
+            checkValidServerReply(leap, mode, stratum, transmitTime);
+
             long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
             // receiveTime = originateTime + transit + skew
             // responseTime = transmitTime + transit - skew
@@ -110,8 +142,10 @@
             //             = (transit + skew - transit + skew)/2
             //             = (2 * skew)/2 = skew
             long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
-            // if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");
-            // if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");
+            if (DBG) {
+                Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
+                        "clock offset: " + clockOffset + "ms");
+            }
 
             // save our results - use the times on this side of the network latency
             // (response rather than request time)
@@ -119,7 +153,7 @@
             mNtpTimeReference = responseTicks;
             mRoundTripTime = roundTripTime;
         } catch (Exception e) {
-            if (false) Log.d(TAG, "request time failed: " + e);
+            if (DBG) Log.d(TAG, "request time failed: " + e);
             return false;
         } finally {
             if (socket != null) {
@@ -158,6 +192,23 @@
         return mRoundTripTime;
     }
 
+    private static void checkValidServerReply(
+            byte leap, byte mode, int stratum, long transmitTime)
+            throws InvalidServerReplyException {
+        if (leap == NTP_LEAP_NOSYNC) {
+            throw new InvalidServerReplyException("unsynchronized server");
+        }
+        if ((mode != NTP_MODE_SERVER) && (mode != NTP_MODE_BROADCAST)) {
+            throw new InvalidServerReplyException("untrusted mode: " + mode);
+        }
+        if ((stratum == NTP_STRATUM_DEATH) || (stratum > NTP_STRATUM_MAX)) {
+            throw new InvalidServerReplyException("untrusted stratum: " + stratum);
+        }
+        if (transmitTime == 0) {
+            throw new InvalidServerReplyException("zero transmitTime");
+        }
+    }
+
     /**
      * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
      */
@@ -177,20 +228,30 @@
     }
 
     /**
-     * Reads the NTP time stamp at the given offset in the buffer and returns 
+     * Reads the NTP time stamp at the given offset in the buffer and returns
      * it as a system time (milliseconds since January 1, 1970).
-     */    
+     */
     private long readTimeStamp(byte[] buffer, int offset) {
         long seconds = read32(buffer, offset);
         long fraction = read32(buffer, offset + 4);
-        return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);        
+        // Special case: zero means zero.
+        if (seconds == 0 && fraction == 0) {
+            return 0;
+        }
+        return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);
     }
 
     /**
-     * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp 
+     * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp
      * at the given offset in the buffer.
-     */    
+     */
     private void writeTimeStamp(byte[] buffer, int offset, long time) {
+        // Special case: zero means zero.
+        if (time == 0) {
+            Arrays.fill(buffer, offset, offset + 8, (byte) 0x00);
+            return;
+        }
+
         long seconds = time / 1000L;
         long milliseconds = time - seconds * 1000L;
         seconds += OFFSET_1900_TO_1970;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8e86a53..1aa5c66 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -169,7 +169,7 @@
     /**
      * Current version of checkin data format.
      */
-    static final String CHECKIN_VERSION = "15";
+    static final String CHECKIN_VERSION = "16";
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -468,8 +468,8 @@
          * @param cluster the index of the CPU cluster.
          * @param step the index of the CPU speed. This is not the actual speed of the CPU.
          * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
-         * @see PowerProfile.getNumCpuClusters()
-         * @see PowerProfile.getNumSpeedStepsInCpuCluster(int)
+         * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
+         * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
          */
         public abstract long getTimeAtCpuSpeed(int cluster, int step, int which);
 
@@ -1135,14 +1135,15 @@
         public static final int STATE2_WIFI_RUNNING_FLAG = 1<<29;
         public static final int STATE2_WIFI_ON_FLAG = 1<<28;
         public static final int STATE2_FLASHLIGHT_FLAG = 1<<27;
-        public static final int STATE2_DEVICE_IDLE_FLAG = 1<<26;
-        public static final int STATE2_CHARGING_FLAG = 1<<25;
-        public static final int STATE2_PHONE_IN_CALL_FLAG = 1<<24;
-        public static final int STATE2_BLUETOOTH_ON_FLAG = 1<<23;
-        public static final int STATE2_CAMERA_FLAG = 1<<22;
+        public static final int STATE2_DEVICE_IDLE_SHIFT = 25;
+        public static final int STATE2_DEVICE_IDLE_MASK = 0x3 << STATE2_DEVICE_IDLE_SHIFT;
+        public static final int STATE2_CHARGING_FLAG = 1<<24;
+        public static final int STATE2_PHONE_IN_CALL_FLAG = 1<<23;
+        public static final int STATE2_BLUETOOTH_ON_FLAG = 1<<22;
+        public static final int STATE2_CAMERA_FLAG = 1<<21;
 
         public static final int MOST_INTERESTING_STATES2 =
-            STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_FLAG
+            STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
             | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
 
         public static final int SETTLE_TO_ZERO_STATES2 = 0xffff0000 & ~MOST_INTERESTING_STATES2;
@@ -1620,36 +1621,57 @@
     public abstract int getPowerSaveModeEnabledCount(int which);
 
     /**
+     * Constant for device idle mode: not active.
+     */
+    public static final int DEVICE_IDLE_MODE_OFF = 0;
+
+    /**
+     * Constant for device idle mode: active in lightweight mode.
+     */
+    public static final int DEVICE_IDLE_MODE_LIGHT = 1;
+
+    /**
+     * Constant for device idle mode: active in full mode.
+     */
+    public static final int DEVICE_IDLE_MODE_FULL = 2;
+
+    /**
      * Returns the time in microseconds that device has been in idle mode while
      * running on battery.
      *
      * {@hide}
      */
-    public abstract long getDeviceIdleModeEnabledTime(long elapsedRealtimeUs, int which);
+    public abstract long getDeviceIdleModeTime(int mode, long elapsedRealtimeUs, int which);
 
     /**
      * Returns the number of times that the devie has gone in to idle mode.
      *
      * {@hide}
      */
-    public abstract int getDeviceIdleModeEnabledCount(int which);
+    public abstract int getDeviceIdleModeCount(int mode, int which);
+
+    /**
+     * Return the longest duration we spent in a particular device idle mode (fully in the
+     * mode, not in idle maintenance etc).
+     */
+    public abstract long getLongestDeviceIdleModeTime(int mode);
 
     /**
      * Returns the time in microseconds that device has been in idling while on
-     * battery.  This is broader than {@link #getDeviceIdleModeEnabledTime} -- it
+     * battery.  This is broader than {@link #getDeviceIdleModeTime} -- it
      * counts all of the time that we consider the device to be idle, whether or not
      * it is currently in the actual device idle mode.
      *
      * {@hide}
      */
-    public abstract long getDeviceIdlingTime(long elapsedRealtimeUs, int which);
+    public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which);
 
     /**
      * Returns the number of times that the devie has started idling.
      *
      * {@hide}
      */
-    public abstract int getDeviceIdlingCount(int which);
+    public abstract int getDeviceIdlingCount(int mode, int which);
 
     /**
      * Returns the number of times that connectivity state changed.
@@ -1847,7 +1869,10 @@
         new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Ww"),
         new BitDescription(HistoryItem.STATE2_WIFI_ON_FLAG, "wifi", "W"),
         new BitDescription(HistoryItem.STATE2_FLASHLIGHT_FLAG, "flashlight", "fl"),
-        new BitDescription(HistoryItem.STATE2_DEVICE_IDLE_FLAG, "device_idle", "di"),
+        new BitDescription(HistoryItem.STATE2_DEVICE_IDLE_MASK,
+                HistoryItem.STATE2_DEVICE_IDLE_SHIFT, "device_idle", "di",
+                new String[] { "off", "light", "full", "???" },
+                new String[] { "off", "light", "full", "???" }),
         new BitDescription(HistoryItem.STATE2_CHARGING_FLAG, "charging", "ch"),
         new BitDescription(HistoryItem.STATE2_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"),
         new BitDescription(HistoryItem.STATE2_BLUETOOTH_ON_FLAG, "bluetooth", "b"),
@@ -2529,8 +2554,14 @@
         final long screenOnTime = getScreenOnTime(rawRealtime, which);
         final long interactiveTime = getInteractiveTime(rawRealtime, which);
         final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
-        final long deviceIdleModeEnabledTime = getDeviceIdleModeEnabledTime(rawRealtime, which);
-        final long deviceIdlingTime = getDeviceIdlingTime(rawRealtime, which);
+        final long deviceIdleModeLightTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdleModeFullTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_FULL,
+                rawRealtime, which);
+        final long deviceLightIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_FULL,
+                rawRealtime, which);
         final int connChanges = getNumConnectivityChange(which);
         final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
 
@@ -2613,11 +2644,15 @@
                 fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000,
                 getMobileRadioActiveTime(rawRealtime, which) / 1000,
                 getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000,
-                powerSaveModeEnabledTime / 1000, connChanges, deviceIdleModeEnabledTime / 1000,
-                getDeviceIdleModeEnabledCount(which), deviceIdlingTime / 1000,
-                getDeviceIdlingCount(which),
+                powerSaveModeEnabledTime / 1000, connChanges, deviceIdleModeFullTime / 1000,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_FULL, which), deviceIdlingTime / 1000,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_FULL, which),
                 getMobileRadioActiveCount(which),
-                getMobileRadioActiveUnknownTime(which) / 1000);
+                getMobileRadioActiveUnknownTime(which) / 1000, deviceIdleModeLightTime / 1000,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which), deviceLightIdlingTime / 1000,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which),
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT),
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_FULL));
         
         // Dump screen brightness stats
         Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -3082,8 +3117,14 @@
         final long screenOnTime = getScreenOnTime(rawRealtime, which);
         final long interactiveTime = getInteractiveTime(rawRealtime, which);
         final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
-        final long deviceIdleModeEnabledTime = getDeviceIdleModeEnabledTime(rawRealtime, which);
-        final long deviceIdlingTime = getDeviceIdlingTime(rawRealtime, which);
+        final long deviceIdleModeLightTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdleModeFullTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_FULL,
+                rawRealtime, which);
+        final long deviceLightIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_FULL,
+                rawRealtime, which);
         final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
         final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which);
         final long wifiOnTime = getWifiOnTime(rawRealtime, which);
@@ -3127,26 +3168,54 @@
                     sb.append(")");
             pw.println(sb.toString());
         }
-        if (deviceIdlingTime != 0) {
+        if (deviceLightIdlingTime != 0) {
             sb.setLength(0);
             sb.append(prefix);
-                    sb.append("  Device idling: ");
-                    formatTimeMs(sb, deviceIdlingTime / 1000);
+                    sb.append("  Device light idling: ");
+                    formatTimeMs(sb, deviceLightIdlingTime / 1000);
                     sb.append("(");
-                    sb.append(formatRatioLocked(deviceIdlingTime, whichBatteryRealtime));
-                    sb.append(") "); sb.append(getDeviceIdlingCount(which));
+                    sb.append(formatRatioLocked(deviceLightIdlingTime, whichBatteryRealtime));
+                    sb.append(") "); sb.append(getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
                     sb.append("x");
             pw.println(sb.toString());
         }
-        if (deviceIdleModeEnabledTime != 0) {
+        if (deviceIdleModeLightTime != 0) {
             sb.setLength(0);
             sb.append(prefix);
-                    sb.append("  Idle mode time: ");
-                    formatTimeMs(sb, deviceIdleModeEnabledTime / 1000);
+                    sb.append("  Idle mode light time: ");
+                    formatTimeMs(sb, deviceIdleModeLightTime / 1000);
                     sb.append("(");
-                    sb.append(formatRatioLocked(deviceIdleModeEnabledTime, whichBatteryRealtime));
-                    sb.append(") "); sb.append(getDeviceIdleModeEnabledCount(which));
+                    sb.append(formatRatioLocked(deviceIdleModeLightTime, whichBatteryRealtime));
+                    sb.append(") ");
+                    sb.append(getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
                     sb.append("x");
+                    sb.append(" -- longest ");
+                    formatTimeMs(sb, getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
+            pw.println(sb.toString());
+        }
+        if (deviceIdlingTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Device full idling: ");
+                    formatTimeMs(sb, deviceIdlingTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(deviceIdlingTime, whichBatteryRealtime));
+                    sb.append(") "); sb.append(getDeviceIdlingCount(DEVICE_IDLE_MODE_FULL, which));
+                    sb.append("x");
+            pw.println(sb.toString());
+        }
+        if (deviceIdleModeFullTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Idle mode full time: ");
+                    formatTimeMs(sb, deviceIdleModeFullTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(deviceIdleModeFullTime, whichBatteryRealtime));
+                    sb.append(") ");
+                    sb.append(getDeviceIdleModeCount(DEVICE_IDLE_MODE_FULL, which));
+                    sb.append("x");
+                    sb.append(" -- longest ");
+                    formatTimeMs(sb, getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_FULL));
             pw.println(sb.toString());
         }
         if (phoneOnTime != 0) {
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 64d6da5..f346fe7 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -276,8 +276,8 @@
     }
 
     /**
-     * Return the primary external storage directory. This directory may not
-     * currently be accessible if it has been mounted by the user on their
+     * Return the primary shared/external storage directory. This directory may
+     * not currently be accessible if it has been mounted by the user on their
      * computer, has been removed from the device, or some other problem has
      * happened. You can determine its current state with
      * {@link #getExternalStorageState()}.
@@ -291,12 +291,15 @@
      * filesystem on a computer.</em>
      * <p>
      * On devices with multiple users (as described by {@link UserManager}),
-     * each user has their own isolated external storage. Applications only have
-     * access to the external storage for the user they're running as.
+     * each user has their own isolated shared storage. Applications only have
+     * access to the shared storage for the user they're running as.
      * <p>
-     * In devices with multiple "external" storage directories, this directory
-     * represents the "primary" external storage that the user will interact
+     * In devices with multiple shared/external storage directories, this
+     * directory represents the primary storage that the user will interact
      * with. Access to secondary storage is available through
+     * {@link Context#getExternalFilesDirs(String)},
+     * {@link Context#getExternalCacheDirs()}, and
+     * {@link Context#getExternalMediaDirs()}.
      * <p>
      * Applications should not directly use this top-level directory, in order
      * to avoid polluting the user's root namespace. Any files that are private
@@ -315,8 +318,9 @@
      * <p>
      * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, if your
      * application only needs to store internal data, consider using
-     * {@link Context#getExternalFilesDir(String)} or
-     * {@link Context#getExternalCacheDir()}, which require no permissions to
+     * {@link Context#getExternalFilesDir(String)},
+     * {@link Context#getExternalCacheDir()}, or
+     * {@link Context#getExternalMediaDirs()}, which require no permissions to
      * read or write.
      * <p>
      * This path may change between platform versions, so applications should
@@ -325,8 +329,7 @@
      * Here is an example of typical code to monitor the state of external
      * storage:
      * <p>
-     * {@sample
-     * development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+     * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * monitor_storage}
      *
      * @see #getExternalStorageState()
@@ -446,32 +449,32 @@
     public static String DIRECTORY_DOCUMENTS = "Documents";
 
     /**
-     * Get a top-level public external storage directory for placing files of
-     * a particular type.  This is where the user will typically place and
-     * manage their own files, so you should be careful about what you put here
-     * to ensure you don't erase their files or get in the way of their own
+     * Get a top-level shared/external storage directory for placing files of a
+     * particular type. This is where the user will typically place and manage
+     * their own files, so you should be careful about what you put here to
+     * ensure you don't erase their files or get in the way of their own
      * organization.
-     * 
-     * <p>On devices with multiple users (as described by {@link UserManager}),
-     * each user has their own isolated external storage. Applications only
-     * have access to the external storage for the user they're running as.</p>
-     *
-     * <p>Here is an example of typical code to manipulate a picture on
-     * the public external storage:</p>
-     * 
+     * <p>
+     * On devices with multiple users (as described by {@link UserManager}),
+     * each user has their own isolated shared storage. Applications only have
+     * access to the shared storage for the user they're running as.
+     * </p>
+     * <p>
+     * Here is an example of typical code to manipulate a picture on the public
+     * shared storage:
+     * </p>
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * public_picture}
      * 
-     * @param type The type of storage directory to return.  Should be one of
-     * {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
-     * {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
-     * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
-     * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or
-     * {@link #DIRECTORY_DCIM}.  May not be null.
-     * 
-     * @return Returns the File path for the directory.  Note that this
-     * directory may not yet exist, so you must make sure it exists before
-     * using it such as with {@link File#mkdirs File.mkdirs()}.
+     * @param type The type of storage directory to return. Should be one of
+     *            {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
+     *            {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
+     *            {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
+     *            {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or
+     *            {@link #DIRECTORY_DCIM}. May not be null.
+     * @return Returns the File path for the directory. Note that this directory
+     *         may not yet exist, so you must make sure it exists before using
+     *         it such as with {@link File#mkdirs File.mkdirs()}.
      */
     public static File getExternalStoragePublicDirectory(String type) {
         throwIfUserRequired();
@@ -623,7 +626,7 @@
     public static final String MEDIA_EJECTING = "ejecting";
 
     /**
-     * Returns the current state of the primary "external" storage device.
+     * Returns the current state of the primary shared/external storage media.
      * 
      * @see #getExternalStorageDirectory()
      * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
@@ -646,8 +649,8 @@
     }
 
     /**
-     * Returns the current state of the storage device that provides the given
-     * path.
+     * Returns the current state of the shared/external storage media at the
+     * given path.
      *
      * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
      *         {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
@@ -665,7 +668,8 @@
     }
 
     /**
-     * Returns whether the primary "external" storage device is removable.
+     * Returns whether the primary shared/external storage media is physically
+     * removable.
      *
      * @return true if the storage device can be removed (such as an SD card),
      *         or false if the storage device is built in and cannot be
@@ -678,8 +682,8 @@
     }
 
     /**
-     * Returns whether the storage device that provides the given path is
-     * removable.
+     * Returns whether the shared/external storage media at the given path is
+     * physically removable.
      *
      * @return true if the storage device can be removed (such as an SD card),
      *         or false if the storage device is built in and cannot be
@@ -697,9 +701,15 @@
     }
 
     /**
-     * Returns whether the primary "external" storage device is emulated. If
-     * true, data stored on this device will be stored on a portion of the
-     * internal storage system.
+     * Returns whether the primary shared/external storage media is emulated.
+     * <p>
+     * The contents of emulated storage devices are backed by a private user
+     * data partition, which means there is little benefit to apps storing data
+     * here instead of the private directories returned by
+     * {@link Context#getFilesDir()}, etc.
+     * <p>
+     * This returns true when emulated storage is backed by either internal
+     * storage or an adopted storage device.
      *
      * @see DevicePolicyManager#setStorageEncryption(android.content.ComponentName,
      *      boolean)
@@ -711,9 +721,16 @@
     }
 
     /**
-     * Returns whether the storage device that provides the given path is
-     * emulated. If true, data stored on this device will be stored on a portion
-     * of the internal storage system.
+     * Returns whether the shared/external storage media at the given path is
+     * emulated.
+     * <p>
+     * The contents of emulated storage devices are backed by a private user
+     * data partition, which means there is little benefit to apps storing data
+     * here instead of the private directories returned by
+     * {@link Context#getFilesDir()}, etc.
+     * <p>
+     * This returns true when emulated storage is backed by either internal
+     * storage or an adopted storage device.
      *
      * @throws IllegalArgumentException if the path is not a valid storage
      *             device.
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 9fdbec3..dd10df3 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -47,6 +47,7 @@
     boolean isPowerSaveMode();
     boolean setPowerSaveMode(boolean mode);
     boolean isDeviceIdleMode();
+    boolean isLightDeviceIdleMode();
 
     void reboot(boolean confirm, String reason, boolean wait);
     void shutdown(boolean confirm, String reason, boolean wait);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 4c19ddd..3ade170 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -46,6 +46,7 @@
     List<UserInfo> getProfiles(int userHandle, boolean enabledOnly);
     boolean canAddMoreManagedProfiles(int userId);
     UserInfo getProfileParent(int userHandle);
+    boolean isSameProfileGroup(int userId, int otherUserId);
     UserInfo getUserInfo(int userHandle);
     long getUserCreationTime(int userHandle);
     boolean isRestricted();
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 1273772b..5852f5f 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntegerRes;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -42,6 +43,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import dalvik.system.VMRuntime;
+
 /**
  * Container for a message (data and object references) that can
  * be sent through an IBinder.  A Parcel can contain both flattened data
@@ -193,6 +196,7 @@
      * indicating that we're responsible for its lifecycle.
      */
     private boolean mOwnsNativeParcelObject;
+    private long mNativeSize;
 
     private RuntimeException mStack;
 
@@ -244,7 +248,7 @@
     private static native int nativeDataAvail(long nativePtr);
     private static native int nativeDataPosition(long nativePtr);
     private static native int nativeDataCapacity(long nativePtr);
-    private static native void nativeSetDataSize(long nativePtr, int size);
+    private static native long nativeSetDataSize(long nativePtr, int size);
     private static native void nativeSetDataPosition(long nativePtr, int pos);
     private static native void nativeSetDataCapacity(long nativePtr, int size);
 
@@ -259,7 +263,7 @@
     private static native void nativeWriteDouble(long nativePtr, double val);
     private static native void nativeWriteString(long nativePtr, String val);
     private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
-    private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
+    private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
 
     private static native byte[] nativeCreateByteArray(long nativePtr);
     private static native byte[] nativeReadBlob(long nativePtr);
@@ -272,13 +276,13 @@
     private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
 
     private static native long nativeCreate();
-    private static native void nativeFreeBuffer(long nativePtr);
+    private static native long nativeFreeBuffer(long nativePtr);
     private static native void nativeDestroy(long nativePtr);
 
     private static native byte[] nativeMarshall(long nativePtr);
-    private static native void nativeUnmarshall(
+    private static native long nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length);
-    private static native void nativeAppendFrom(
+    private static native long nativeAppendFrom(
             long thisNativePtr, long otherNativePtr, int offset, int length);
     private static native boolean nativeHasFileDescriptors(long nativePtr);
     private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
@@ -390,7 +394,7 @@
      * @param size The new number of bytes in the Parcel.
      */
     public final void setDataSize(int size) {
-        nativeSetDataSize(mNativePtr, size);
+        updateNativeSize(nativeSetDataSize(mNativePtr, size));
     }
 
     /**
@@ -442,11 +446,11 @@
      * Set the bytes in data to be the raw bytes of this Parcel.
      */
     public final void unmarshall(byte[] data, int offset, int length) {
-        nativeUnmarshall(mNativePtr, data, offset, length);
+        updateNativeSize(nativeUnmarshall(mNativePtr, data, offset, length));
     }
 
     public final void appendFrom(Parcel parcel, int offset, int length) {
-        nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
+        updateNativeSize(nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length));
     }
 
     /**
@@ -599,7 +603,24 @@
      * if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p>
      */
     public final void writeFileDescriptor(FileDescriptor val) {
-        nativeWriteFileDescriptor(mNativePtr, val);
+        updateNativeSize(nativeWriteFileDescriptor(mNativePtr, val));
+    }
+
+    private void updateNativeSize(long newNativeSize) {
+        if (mOwnsNativeParcelObject) {
+            if (newNativeSize > Integer.MAX_VALUE) {
+                newNativeSize = Integer.MAX_VALUE;
+            }
+            if (newNativeSize != mNativeSize) {
+                int delta = (int) (newNativeSize - mNativeSize);
+                if (delta > 0) {
+                    VMRuntime.getRuntime().registerNativeAllocation(delta);
+                } else {
+                    VMRuntime.getRuntime().registerNativeFree(-delta);
+                }
+                mNativeSize = newNativeSize;
+            }
+        }
     }
 
     /**
@@ -2545,7 +2566,7 @@
 
     private void freeBuffer() {
         if (mOwnsNativeParcelObject) {
-            nativeFreeBuffer(mNativePtr);
+            updateNativeSize(nativeFreeBuffer(mNativePtr));
         }
     }
 
@@ -2553,6 +2574,7 @@
         if (mNativePtr != 0) {
             if (mOwnsNativeParcelObject) {
                 nativeDestroy(mNativePtr);
+                updateNativeSize(0);
             }
             mNativePtr = 0;
         }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 69974fa..1cffa83 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -910,6 +910,26 @@
     }
 
     /**
+     * Returns true if the device is currently in light idle mode.  This happens when a device
+     * has had its screen off for a short time, switching it into a batching mode where we
+     * execute jobs, syncs, networking on a batching schedule.  You can monitor for changes to
+     * this state with {@link #ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED}.
+     *
+     * @return Returns true if currently in active light device idle mode, else false.  This is
+     * when light idle mode restrictions are being actively applied; it will return false if the
+     * device is in a long-term idle mode but currently running a maintenance window where
+     * restrictions have been lifted.
+     * @hide
+     */
+    public boolean isLightDeviceIdleMode() {
+        try {
+            return mService.isLightDeviceIdleMode();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
      * Return whether the given application package name is on the device's power whitelist.
      * Apps can be placed on the whitelist through the settings UI invoked by
      * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
@@ -961,6 +981,15 @@
             = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
 
     /**
+     * Intent that is broadcast when the state of {@link #isLightDeviceIdleMode()} changes.
+     * This broadcast is only sent to registered receivers.
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED
+            = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+
+    /**
      * @hide Intent that is broadcast when the set of power save whitelist apps has changed.
      * This broadcast is only sent to registered receivers.
      */
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index b6d0fcb..9801e1b 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -148,7 +148,9 @@
         public void onLowPowerModeChanged(boolean enabled);
     }
 
-    public abstract void setDeviceIdleMode(boolean enabled);
+    public abstract boolean setDeviceIdleMode(boolean enabled);
+
+    public abstract boolean setLightDeviceIdleMode(boolean enabled);
 
     public abstract void setDeviceIdleWhitelist(int[] appids);
 
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 41de579..4535572 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -341,6 +341,10 @@
         } finally {
             uncryptFile.close();
         }
+        // UNCRYPT_FILE needs to be readable by system server on bootup.
+        if (!UNCRYPT_FILE.setReadable(true, false)) {
+            Log.e(TAG, "Error setting readable for " + UNCRYPT_FILE.getCanonicalPath());
+        }
         Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
 
         // If the package is on the /data partition, write the block map file
@@ -501,6 +505,25 @@
             Log.e(TAG, "Error reading recovery log", e);
         }
 
+        if (UNCRYPT_FILE.exists()) {
+            String filename = null;
+            try {
+                filename = FileUtils.readTextFile(UNCRYPT_FILE, 0, null);
+            } catch (IOException e) {
+                Log.e(TAG, "Error reading uncrypt file", e);
+            }
+
+            // Remove the OTA package on /data that has been (possibly
+            // partially) processed. (Bug: 24973532)
+            if (filename != null && filename.startsWith("/data")) {
+                if (UNCRYPT_FILE.delete()) {
+                    Log.i(TAG, "Deleted: " + filename);
+                } else {
+                    Log.e(TAG, "Can't delete: " + filename);
+                }
+            }
+        }
+
         // Delete everything in RECOVERY_DIR except those beginning
         // with LAST_PREFIX
         String[] names = RECOVERY_DIR.list();
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 796addc..95da438 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -270,13 +270,24 @@
      * Returns true if this UserHandle refers to the owner user; false otherwise.
      * @return true if this UserHandle refers to the owner user; false otherwise.
      * @hide
-     * TODO: find an alternative to this Api.
+     * @deprecated please use {@link #isSystem()} or check for
+     * {@link android.content.pm.UserInfo#isPrimary()}
+     * {@link android.content.pm.UserInfo#isAdmin()} based on your particular use case.
      */
     @SystemApi
     public boolean isOwner() {
         return this.equals(OWNER);
     }
 
+    /**
+     * @return true if this UserHandle refers to the system user; false otherwise.
+     * @hide
+     */
+    @SystemApi
+    public boolean isSystem() {
+        return this.equals(SYSTEM);
+    }
+
     /** @hide */
     public UserHandle(int h) {
         mHandle = h;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d178d20..1c1575e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1093,6 +1093,22 @@
     }
 
     /**
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @param userId one of the two user ids to check.
+     * @param otherUserId one of the two user ids to check.
+     * @return true if the two user ids are in the same profile group.
+     * @hide
+     */
+    public boolean isSameProfileGroup(int userId, int otherUserId) {
+        try {
+            return mService.isSameProfileGroup(userId, otherUserId);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get user list", re);
+            return false;
+        }
+    }
+
+    /**
      * Returns list of the profiles of userHandle including
      * userHandle itself.
      * Note that this returns only enabled.
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 979c828..2445bc2 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -383,6 +383,7 @@
                 final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
                 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
                 filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
+                filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
                 mContext.registerReceiver(this, filter);
             } else {
                 mContext.unregisterReceiver(this);
@@ -395,13 +396,7 @@
             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
-                final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
-                        : (streamType == mStreamType);
-                if (mSeekBar != null && streamMatch && streamValue != -1) {
-                    final boolean muted = mAudioManager.isStreamMute(mStreamType)
-                            || streamValue == 0;
-                    mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
-                }
+                updateVolumeSlider(streamType, streamValue);
             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
                 if (mNotificationOrRing) {
                     mRingerMode = mAudioManager.getRingerModeInternal();
@@ -409,10 +404,24 @@
                 if (mAffectedByRingerMode) {
                     updateSlider();
                 }
+            } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
+                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                int streamVolume = mAudioManager.getStreamVolume(streamType);
+                updateVolumeSlider(streamType, streamVolume);
             } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
                 mZenMode = mNotificationManager.getZenMode();
                 updateSlider();
             }
         }
+
+        private void updateVolumeSlider(int streamType, int streamValue) {
+            final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
+                    : (streamType == mStreamType);
+            if (mSeekBar != null && streamMatch && streamValue != -1) {
+                final boolean muted = mAudioManager.isStreamMute(mStreamType)
+                        || streamValue == 0;
+                mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
+            }
+        }
     }
 }
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 342f8c7..4b63c36 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -38,7 +38,6 @@
 import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
@@ -406,6 +405,14 @@
         public static final String SUB_ID = "sub_id";
 
         /**
+         * The post-dial portion of a dialed number, including any digits dialed after a
+         * {@link TelecomManager#DTMF_CHARACTER_PAUSE} or a {@link
+         * TelecomManager#DTMF_CHARACTER_WAIT} and these characters themselves.
+         * <P>Type: TEXT</P>
+         */
+        public static final String POST_DIAL_DIGITS = "post_dial_digits";
+
+        /**
          * If a successful call is made that is longer than this duration, update the phone number
          * in the ContactsProvider with the normalized version of the number, based on the user's
          * current country code.
@@ -436,7 +443,7 @@
         public static Uri addCall(CallerInfo ci, Context context, String number,
                 int presentation, int callType, int features, PhoneAccountHandle accountHandle,
                 long start, int duration, Long dataUsage) {
-            return addCall(ci, context, number, presentation, callType, features, accountHandle,
+            return addCall(ci, context, number, "", presentation, callType, features, accountHandle,
                     start, duration, dataUsage, false, false);
         }
 
@@ -466,10 +473,11 @@
          * {@hide}
          */
         public static Uri addCall(CallerInfo ci, Context context, String number,
-                                  int presentation, int callType, int features, PhoneAccountHandle accountHandle,
-                                  long start, int duration, Long dataUsage, boolean addForAllUsers) {
-            return addCall(ci, context, number, presentation, callType, features, accountHandle,
-                    start, duration, dataUsage, addForAllUsers, false);
+                String postDialDigits, int presentation, int callType, int features,
+                PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
+                boolean addForAllUsers) {
+            return addCall(ci, context, number, postDialDigits, presentation, callType, features,
+                    accountHandle, start, duration, dataUsage, addForAllUsers, false);
         }
 
         /**
@@ -479,6 +487,8 @@
          * if the contact is unknown.
          * @param context the context used to get the ContentResolver
          * @param number the phone number to be added to the calls db
+         * @param postDialDigits the post-dial digits that were dialed after the number,
+         *        if it was outgoing. Otherwise it is ''.
          * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which
          *        is set by the network and denotes the number presenting rules for
          *        "allowed", "payphone", "restricted" or "unknown"
@@ -499,8 +509,9 @@
          * {@hide}
          */
         public static Uri addCall(CallerInfo ci, Context context, String number,
-                int presentation, int callType, int features, PhoneAccountHandle accountHandle,
-                long start, int duration, Long dataUsage, boolean addForAllUsers, boolean is_read) {
+                String postDialDigits, int presentation, int callType, int features,
+                PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
+                boolean addForAllUsers, boolean is_read) {
             final ContentResolver resolver = context.getContentResolver();
             int numberPresentation = PRESENTATION_ALLOWED;
 
@@ -551,6 +562,7 @@
             ContentValues values = new ContentValues(6);
 
             values.put(NUMBER, number);
+            values.put(POST_DIAL_DIGITS, postDialDigits);
             values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
             values.put(TYPE, Integer.valueOf(callType));
             values.put(FEATURES, features);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d601831..11effd0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1434,25 +1434,6 @@
     }
 
     /**
-     * An app can use this method to check if it is currently allowed to change the network
-     * state. In order to be allowed to do so, an app must first declare either the
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} or
-     * {@link android.Manifest.permission#WRITE_SETTINGS} permission in its manifest. If it
-     * is currently disallowed, it can prompt the user to grant it this capability through a
-     * management UI by sending an Intent with action
-     * {@link android.provider.Settings#ACTION_MANAGE_WRITE_SETTINGS}.
-     *
-     * @param context A context
-     * @return true if the calling app can change the state of network, false otherwise.
-     * @hide
-     */
-    public static boolean canChangeNetworkState(Context context) {
-        int uid = Binder.getCallingUid();
-        return Settings.isCallingPackageAllowedToChangeNetworkState(context, uid, Settings
-                .getPackageNameForUid(context, uid), false);
-    }
-
-    /**
      * System settings, containing miscellaneous system preferences.  This
      * table holds simple name/value pairs.  There are convenience
      * functions for accessing individual settings entries.
@@ -8384,7 +8365,7 @@
      * write/modify system settings, as the condition differs for pre-M, M+, and
      * privileged/preinstalled apps. If the provided uid does not match the
      * callingPackage, a negative result will be returned. The caller is expected to have
-     * either WRITE_SETTINGS or CHANGE_NETWORK_STATE permission declared.
+     * the WRITE_SETTINGS permission declared.
      *
      * Note: if the check is successful, the operation of this app will be updated to the
      * current time.
@@ -8400,31 +8381,22 @@
     /**
      * Performs a strict and comprehensive check of whether a calling package is allowed to
      * change the state of network, as the condition differs for pre-M, M+, and
-     * privileged/preinstalled apps. If the provided uid does not match the
-     * callingPackage, a negative result will be returned. The caller is expected to have
-     * either of CHANGE_NETWORK_STATE or WRITE_SETTINGS permission declared.
-     * @hide
-     */
-    public static boolean isCallingPackageAllowedToChangeNetworkState(Context context, int uid,
-            String callingPackage, boolean throwException) {
-        return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
-                callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
-                PM_CHANGE_NETWORK_STATE, false);
-    }
-
-    /**
-     * Performs a strict and comprehensive check of whether a calling package is allowed to
-     * change the state of network, as the condition differs for pre-M, M+, and
-     * privileged/preinstalled apps. If the provided uid does not match the
-     * callingPackage, a negative result will be returned. The caller is expected to have
-     * either CHANGE_NETWORK_STATE or WRITE_SETTINGS permission declared.
+     * privileged/preinstalled apps. The caller is expected to have either the
+     * CHANGE_NETWORK_STATE or the WRITE_SETTINGS permission declared. Either of these
+     * permissions allow changing network state; WRITE_SETTINGS is a runtime permission and
+     * can be revoked, but (except in M, excluding M MRs), CHANGE_NETWORK_STATE is a normal
+     * permission and cannot be revoked. See http://b/23597341
      *
-     * Note: if the check is successful, the operation of this app will be updated to the
-     * current time.
+     * Note: if the check succeeds because the application holds WRITE_SETTINGS, the operation
+     * of this app will be updated to the current time.
      * @hide
      */
     public static boolean checkAndNoteChangeNetworkStateOperation(Context context, int uid,
             String callingPackage, boolean throwException) {
+        if (context.checkCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
         return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
                 callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
                 PM_CHANGE_NETWORK_STATE, true);
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 2c49d16..82f1b28 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -41,6 +41,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.GregorianCalendar;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.UUID;
@@ -661,7 +662,7 @@
             int userHandle, boolean shortVersion) {
         final int num;
         String summary, line1, line2;
-        final CharSequence formattedTime = getFormattedTime(context, time, minutes, userHandle);
+        final CharSequence formattedTime = getFormattedTime(context, time, userHandle);
         final Resources res = context.getResources();
         if (minutes < 60) {
             // display as minutes
@@ -694,8 +695,7 @@
 
     public static Condition toNextAlarmCondition(Context context, long now, long alarm,
             int userHandle) {
-        int minutes = Math.round((alarm-now) / (float) MINUTES_MS);
-        final CharSequence formattedTime = getFormattedTime(context, alarm, minutes, userHandle);
+        final CharSequence formattedTime = getFormattedTime(context, alarm, userHandle);
         final Resources res = context.getResources();
         final String line1 = res.getString(R.string.zen_mode_alarm, formattedTime);
         final Uri id = toCountdownConditionId(alarm);
@@ -703,11 +703,15 @@
                 Condition.FLAG_RELEVANT_NOW);
     }
 
-    private static CharSequence getFormattedTime(Context context, long time, int minutes,
-            int userHandle) {
-        String skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma";
-        if (minutes > DAY_MINUTES) {
-            skeleton = "EEE " + (DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma");
+    private static CharSequence getFormattedTime(Context context, long time, int userHandle) {
+        String skeleton = "EEE " + (DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma");
+        GregorianCalendar now = new GregorianCalendar();
+        GregorianCalendar endTime = new GregorianCalendar();
+        endTime.setTimeInMillis(time);
+        if (now.get(Calendar.YEAR) == endTime.get(Calendar.YEAR)
+                && now.get(Calendar.MONTH) == endTime.get(Calendar.MONTH)
+                && now.get(Calendar.DATE) == endTime.get(Calendar.DATE)) {
+            skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma";
         }
         final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
         return DateFormat.format(pattern, time);
diff --git a/core/java/android/test/AndroidTestCase.java b/core/java/android/test/AndroidTestCase.java
index 0635559..2ecbfae 100644
--- a/core/java/android/test/AndroidTestCase.java
+++ b/core/java/android/test/AndroidTestCase.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.test.suitebuilder.annotation.Suppress;
 
 import junit.framework.TestCase;
 
@@ -44,6 +45,7 @@
         super.tearDown();
     }
 
+    @Suppress
     public void testAndroidTestCaseSetupProperly() {
         assertNotNull("Context is null. setContext should be called before tests are run",
                 mContext);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6ece091..6a33579 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -627,7 +627,9 @@
 
                 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
 
-                if (chooseHt.length != 0) {
+                if (chooseHt.length == 0) {
+                    chooseHt = null; // So that out() would not assume it has any contents
+                } else {
                     if (chooseHtv == null ||
                         chooseHtv.length < chooseHt.length) {
                         chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
@@ -810,7 +812,7 @@
 
                     v = out(source, here, endPos,
                             fmAscent, fmDescent, fmTop, fmBottom,
-                            v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
+                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
                             needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
                             chs, widths, paraStart, ellipsize, ellipsizedWidth,
                             lineWidths[breakIndex], paint, moreChars);
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index c82587b..6d1d893 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -432,23 +432,24 @@
                 return anim;
             }
         } else {
-            int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
-            int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
-            int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
-            int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
+            sceneRoot.getLocationInWindow(tempLocation);
+            int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
+            int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
+            int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
+            int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
             // TODO: also handle size changes: check bounds and animate size changes
             if (startX != endX || startY != endY) {
-                sceneRoot.getLocationInWindow(tempLocation);
-                Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
-                        Bitmap.Config.ARGB_8888);
+                final int width = view.getWidth();
+                final int height = view.getHeight();
+                Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                 Canvas canvas = new Canvas(bitmap);
                 view.draw(canvas);
                 final BitmapDrawable drawable = new BitmapDrawable(bitmap);
+                drawable.setBounds(startX, startY, startX + width, startY + height);
                 final float transitionAlpha = view.getTransitionAlpha();
                 view.setTransitionAlpha(0);
                 sceneRoot.getOverlay().add(drawable);
-                Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0],
-                        startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]);
+                Path topLeftPath = getPathMotion().getPath(startX, startY, endX, endY);
                 PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
                         DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
                 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 379651e..0d5c135 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -37,6 +37,11 @@
  */
 public final class LocaleList {
     private final Locale[] mList;
+    // This is a comma-separated list of the locales in the LocaleList created at construction time,
+    // basically the result of running each locale's toLanguageTag() method and concatenating them
+    // with commas in between.
+    private final String mStringRepresentation;
+
     private static final Locale[] sEmptyList = new Locale[0];
     private static final LocaleList sEmptyLocaleList = new LocaleList();
 
@@ -95,15 +100,9 @@
         return sb.toString();
     }
 
+    @NonNull
     public String toLanguageTags() {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < mList.length; ++i) {
-            sb.append(mList[i].toLanguageTag());
-            if (i < mList.length - 1) {
-                sb.append(',');
-            }
-        }
-        return sb.toString();
+        return mStringRepresentation;
     }
 
     /**
@@ -112,6 +111,7 @@
      */
     public LocaleList() {
         mList = sEmptyList;
+        mStringRepresentation = "";
     }
 
     /**
@@ -121,9 +121,11 @@
     public LocaleList(@Nullable Locale locale) {
         if (locale == null) {
             mList = sEmptyList;
+            mStringRepresentation = "";
         } else {
             mList = new Locale[1];
             mList[0] = (Locale) locale.clone();
+            mStringRepresentation = locale.toLanguageTag();
         }
     }
 
@@ -134,9 +136,11 @@
     public LocaleList(@Nullable Locale[] list) {
         if (list == null || list.length == 0) {
             mList = sEmptyList;
+            mStringRepresentation = "";
         } else {
             final Locale[] localeList = new Locale[list.length];
             final HashSet<Locale> seenLocales = new HashSet<Locale>();
+            final StringBuilder sb = new StringBuilder();
             for (int i = 0; i < list.length; ++i) {
                 final Locale l = list[i];
                 if (l == null) {
@@ -144,11 +148,17 @@
                 } else if (seenLocales.contains(l)) {
                     throw new IllegalArgumentException();
                 } else {
-                    seenLocales.add(l);
-                    localeList[i] = (Locale) l.clone();
+                    final Locale localeClone = (Locale) l.clone();
+                    localeList[i] = localeClone;
+                    sb.append(localeClone.toLanguageTag());
+                    if (i < list.length - 1) {
+                        sb.append(',');
+                    }
+                    seenLocales.add(localeClone);
                 }
             }
             mList = localeList;
+            mStringRepresentation = sb.toString();
         }
     }
 
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index 954dcfb..cebb8f0 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -660,7 +660,7 @@
             // and http://www.spaceroots.org/documents/ellipse/node22.html
 
             // Maximum of 45 degrees per cubic Bezier segment
-            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+            int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
 
             double eta1 = start;
             double cosTheta = Math.cos(theta);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index cc4598d..bae51d3 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -766,6 +766,15 @@
     }
 
     /**
+     * Sets the current pointer shape.
+     * @param pointerShape the id of the pointer icon.
+     * @hide
+     */
+    public void setPointerShape(int pointerShape) {
+        InputManager.getInstance().setPointerIconShape(pointerShape);
+    }
+
+    /**
      * Provides information about the range of values for a particular {@link MotionEvent} axis.
      *
      * @see InputDevice#getMotionRange(int)
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1c20cab..55cf56f 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -790,8 +790,14 @@
     public static final int KEYCODE_MEDIA_STEP_BACKWARD = 275;
     /** Key code constant: put device to sleep unless a wakelock is held. */
     public static final int KEYCODE_SOFT_SLEEP = 276;
+    /** Key code constant: Cut key. */
+    public static final int KEYCODE_CUT = 277;
+    /** Key code constant: Copy key. */
+    public static final int KEYCODE_COPY = 278;
+    /** Key code constant: Paste key. */
+    public static final int KEYCODE_PASTE = 279;
 
-    private static final int LAST_KEYCODE = KEYCODE_SOFT_SLEEP;
+    private static final int LAST_KEYCODE = KEYCODE_PASTE;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index e200bef..aa29636 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -547,9 +547,9 @@
                 // Don't retain static reference on context.
                 mConstructorArgs[0] = lastContext;
                 mConstructorArgs[1] = null;
-            }
 
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
 
             return result;
         }
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index cf35ce5..88e9a95 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -48,6 +48,12 @@
     /** Style constant: Null icon.  It has no bitmap. */
     public static final int STYLE_NULL = 0;
 
+    /** Style constant: no icons are specified. If all views uses this, then falls back
+     * to the default style, but this is helpful to distinguish a view explicitly want
+     * to have the default icon.
+     */
+    public static final int STYLE_NOT_SPECIFIED = 1;
+
     /** Style constant: Arrow icon.  (Default mouse pointer) */
     public static final int STYLE_ARROW = 1000;
 
@@ -60,12 +66,74 @@
     /** {@hide} Style constant: Spot anchor icon for touchpads. */
     public static final int STYLE_SPOT_ANCHOR = 2002;
 
+    // Style constants for additional predefined icons for mice.
+    /** Style constant: context-menu. */
+    public static final int STYLE_CONTEXT_MENU = 1001;
+
+    /** Style constant: hand. */
+    public static final int STYLE_HAND = 1002;
+
+    /** Style constant: help. */
+    public static final int STYLE_HELP = 1003;
+
+    /** Style constant: wait. */
+    public static final int STYLE_WAIT = 1004;
+
+    /** Style constant: cell. */
+    public static final int STYLE_CELL = 1006;
+
+    /** Style constant: crosshair. */
+    public static final int STYLE_CROSSHAIR = 1007;
+
+    /** Style constant: text. */
+    public static final int STYLE_TEXT = 1008;
+
+    /** Style constant: vertical-text. */
+    public static final int STYLE_VERTICAL_TEXT = 1009;
+
+    /** Style constant: alias (indicating an alias of/shortcut to something is
+      * to be created. */
+    public static final int STYLE_ALIAS = 1010;
+
+    /** Style constant: copy. */
+    public static final int STYLE_COPY = 1011;
+
+    /** Style constant: no-drop. */
+    public static final int STYLE_NO_DROP = 1012;
+
+    /** Style constant: all-scroll. */
+    public static final int STYLE_ALL_SCROLL = 1013;
+
+    /** Style constant: horizontal double arrow mainly for resizing. */
+    public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014;
+
+    /** Style constant: vertical double arrow mainly for resizing. */
+    public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015;
+
+    /** Style constant: diagonal double arrow -- top-right to bottom-left. */
+    public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
+
+    /** Style constant: diagonal double arrow -- top-left to bottom-right. */
+    public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
+
+    /** Style constant: zoom-in. */
+    public static final int STYLE_ZOOM_IN = 1018;
+
+    /** Style constant: zoom-out. */
+    public static final int STYLE_ZOOM_OUT = 1019;
+
+    /** Style constant: grab. */
+    public static final int STYLE_GRAB = 1020;
+
+    /** Style constant: grabbing. */
+    public static final int STYLE_GRABBING = 1021;
+
     // OEM private styles should be defined starting at this range to avoid
     // conflicts with any system styles that may be defined in the future.
     private static final int STYLE_OEM_FIRST = 10000;
 
-    // The default pointer icon.
-    private static final int STYLE_DEFAULT = STYLE_ARROW;
+    /** {@hide} The default pointer icon. */
+    public static final int STYLE_DEFAULT = STYLE_ARROW;
 
     private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
 
@@ -434,6 +502,49 @@
                 return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
             case STYLE_SPOT_ANCHOR:
                 return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+            case STYLE_HAND:
+                return com.android.internal.R.styleable.Pointer_pointerIconHand;
+            case STYLE_CONTEXT_MENU:
+                return com.android.internal.R.styleable.Pointer_pointerIconContextMenu;
+            case STYLE_HELP:
+                return com.android.internal.R.styleable.Pointer_pointerIconHelp;
+            case STYLE_WAIT:
+                // falls back to the default icon because no animation support.
+                return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+            case STYLE_CELL:
+                return com.android.internal.R.styleable.Pointer_pointerIconCell;
+            case STYLE_CROSSHAIR:
+                return com.android.internal.R.styleable.Pointer_pointerIconCrosshair;
+            case STYLE_TEXT:
+                return com.android.internal.R.styleable.Pointer_pointerIconText;
+            case STYLE_VERTICAL_TEXT:
+                return com.android.internal.R.styleable.Pointer_pointerIconVerticalText;
+            case STYLE_ALIAS:
+                return com.android.internal.R.styleable.Pointer_pointerIconAlias;
+            case STYLE_COPY:
+                return com.android.internal.R.styleable.Pointer_pointerIconCopy;
+            case STYLE_ALL_SCROLL:
+                return com.android.internal.R.styleable.Pointer_pointerIconAllScroll;
+            case STYLE_NO_DROP:
+                return com.android.internal.R.styleable.Pointer_pointerIconNodrop;
+            case STYLE_HORIZONTAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow;
+            case STYLE_VERTICAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow;
+            case STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.
+                        Pointer_pointerIconTopRightDiagonalDoubleArrow;
+            case STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW:
+                return com.android.internal.R.styleable.
+                        Pointer_pointerIconTopLeftDiagonalDoubleArrow;
+            case STYLE_ZOOM_IN:
+                return com.android.internal.R.styleable.Pointer_pointerIconZoomIn;
+            case STYLE_ZOOM_OUT:
+                return com.android.internal.R.styleable.Pointer_pointerIconZoomOut;
+            case STYLE_GRAB:
+                return com.android.internal.R.styleable.Pointer_pointerIconGrab;
+            case STYLE_GRABBING:
+                return com.android.internal.R.styleable.Pointer_pointerIconGrabbing;
             default:
                 return 0;
         }
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 771856f..88bffb5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -198,8 +198,8 @@
      * @see #isValid()
      */
     public void end(DisplayListCanvas canvas) {
-        long renderNodeData = canvas.finishRecording();
-        nSetDisplayListData(mNativeRenderNode, renderNodeData);
+        long displayList = canvas.finishRecording();
+        nSetDisplayList(mNativeRenderNode, displayList);
         canvas.recycle();
         mValid = true;
     }
@@ -209,10 +209,10 @@
      * during destruction of hardware resources, to ensure that we do not hold onto
      * obsolete resources after related resources are gone.
      */
-    public void destroyDisplayListData() {
+    public void discardDisplayList() {
         if (!mValid) return;
 
-        nSetDisplayListData(mNativeRenderNode, 0);
+        nSetDisplayList(mNativeRenderNode, 0);
         mValid = false;
     }
 
@@ -781,7 +781,7 @@
 
     private static native long nCreate(String name);
     private static native void nDestroyRenderNode(long renderNode);
-    private static native void nSetDisplayListData(long renderNode, long newData);
+    private static native void nSetDisplayList(long renderNode, long newData);
 
     // Matrix
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7752ed8..fa86c74 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15385,11 +15385,11 @@
 
     private void resetDisplayList() {
         if (mRenderNode.isValid()) {
-            mRenderNode.destroyDisplayListData();
+            mRenderNode.discardDisplayList();
         }
 
         if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) {
-            mBackgroundRenderNode.destroyDisplayListData();
+            mBackgroundRenderNode.discardDisplayList();
         }
     }
 
@@ -20995,6 +20995,11 @@
         }
     }
 
+    /** @hide */
+    public int getPointerShape(MotionEvent event, float x, float y) {
+        return PointerIcon.STYLE_NOT_SPECIFIED;
+    }
+
     //
     // Properties
     //
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 475ce2f..7c7ad91 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1715,6 +1715,36 @@
         return false;
     }
 
+    /** @hide */
+    @Override
+    public int getPointerShape(MotionEvent event, float x, float y) {
+        // Check what the child under the pointer says about the pointer.
+        final int childrenCount = mChildrenCount;
+        if (childrenCount != 0) {
+            final ArrayList<View> preorderedList = buildOrderedChildList();
+            final boolean customOrder = preorderedList == null
+                    && isChildrenDrawingOrderEnabled();
+            final View[] children = mChildren;
+            for (int i = childrenCount - 1; i >= 0; i--) {
+                final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+                final View child = (preorderedList == null)
+                        ? children[childIndex] : preorderedList.get(childIndex);
+                PointF point = getLocalPoint();
+                if (isTransformedTouchPointInView(x, y, child, point)) {
+                    final int pointerShape = child.getPointerShape(event, point.x, point.y);
+                    if (pointerShape != PointerIcon.STYLE_NOT_SPECIFIED) {
+                        return pointerShape;
+                    }
+                    break;
+                }
+            }
+        }
+
+        // The pointer is not a child or the child has no preferences, returning the default
+        // implementation.
+        return super.getPointerShape(event, x, y);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -4606,7 +4636,6 @@
     }
 
     private void removeViewInternal(int index, View view) {
-
         if (mTransition != null) {
             mTransition.removeChild(this, view);
         }
@@ -4699,12 +4728,17 @@
     }
 
     private void removeViewsInternal(int start, int count) {
+        final int end = start + count;
+
+        if (start < 0 || count < 0 || end > mChildrenCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
         final View focused = mFocused;
         final boolean detach = mAttachInfo != null;
         boolean clearChildFocus = false;
 
         final View[] children = mChildren;
-        final int end = start + count;
 
         for (int i = start; i < end; i++) {
             final View view = children[i];
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3d10289..7d39a0c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -37,6 +37,7 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Build;
@@ -324,6 +325,8 @@
     private long mFpsPrevTime = -1;
     private int mFpsNumFrames;
 
+    private int mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED;
+
     /**
      * see {@link #playSoundEffect(int)}
      */
@@ -4168,6 +4171,33 @@
         private int processPointerEvent(QueuedInputEvent q) {
             final MotionEvent event = (MotionEvent)q.mEvent;
 
+            if (event.getPointerCount() == 1
+                    && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
+                        || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
+                    // Other apps or the window manager may change the icon shape outside of
+                    // this app, therefore the icon shape has to be reset on enter/exit event.
+                    mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED;
+                }
+
+                final float x = event.getX();
+                final float y = event.getY();
+                if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT
+                        && x >= 0 && x < mView.getWidth() && y >= 0 && y < mView.getHeight()) {
+                    int pointerShape = mView.getPointerShape(event, x, y);
+                    if (pointerShape == PointerIcon.STYLE_NOT_SPECIFIED) {
+                        pointerShape = PointerIcon.STYLE_DEFAULT;
+                    }
+
+                    if (mPointerIconShape != pointerShape) {
+                        mPointerIconShape = pointerShape;
+                        event.getDevice().setPointerShape(pointerShape);
+                    }
+                } else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
+                    mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED;
+                }
+            }
+
             mAttachInfo.mUnbufferedDispatchRequested = false;
             boolean handled = mView.dispatchPointerEvent(event);
             if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 13544851..d9ec866 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -666,7 +666,7 @@
     void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
         CharSequence curTitle = wp.getTitle();
         if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
-            wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
             if (wp.token == null) {
                 View decor = peekDecorView();
                 if (decor != null) {
@@ -674,24 +674,38 @@
                 }
             }
             if (curTitle == null || curTitle.length() == 0) {
-                String title;
+                final StringBuilder title = new StringBuilder(32);
                 if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
-                    title="Media";
+                    title.append("Media");
                 } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
-                    title="MediaOvr";
+                    title.append("MediaOvr");
                 } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
-                    title="Panel";
+                    title.append("Panel");
                 } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
-                    title="SubPanel";
+                    title.append("SubPanel");
                 } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) {
-                    title="AboveSubPanel";
+                    title.append("AboveSubPanel");
                 } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
-                    title="AtchDlg";
+                    title.append("AtchDlg");
                 } else {
-                    title=Integer.toString(wp.type);
+                    title.append(wp.type);
                 }
                 if (mAppName != null) {
-                    title += ":" + mAppName;
+                    title.append(":").append(mAppName);
+                }
+                wp.setTitle(title);
+            }
+        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
+                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
+            // We don't set the app token to this system window because the life cycles should be
+            // independent. If an app creates a system window and then the app goes to the stopped
+            // state, the system window should not be affected (can still show and receive input
+            // events).
+            if (curTitle == null || curTitle.length() == 0) {
+                final StringBuilder title = new StringBuilder(32);
+                title.append("Sys").append(wp.type);
+                if (mAppName != null) {
+                    title.append(":").append(mAppName);
                 }
                 wp.setTitle(title);
             }
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index d0dde00..1da305f 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -43,6 +43,8 @@
     private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
     private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
 
+    private boolean mIsAllWindowsCached;
+
     private final SparseArray<AccessibilityWindowInfo> mWindowCache =
             new SparseArray<>();
 
@@ -52,6 +54,24 @@
     private final SparseArray<AccessibilityWindowInfo> mTempWindowArray =
             new SparseArray<>();
 
+    public void setWindows(List<AccessibilityWindowInfo> windows) {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Set windows");
+            }
+            clearWindowCache();
+            if (windows == null) {
+                return;
+            }
+            final int windowCount = windows.size();
+            for (int i = 0; i < windowCount; i++) {
+                final AccessibilityWindowInfo window = windows.get(i);
+                addWindow(window);
+            }
+            mIsAllWindowsCached = true;
+        }
+    }
+
     public void addWindow(AccessibilityWindowInfo window) {
         synchronized (mLock) {
             if (DEBUG) {
@@ -186,6 +206,10 @@
 
     public List<AccessibilityWindowInfo> getWindows() {
         synchronized (mLock) {
+            if (!mIsAllWindowsCached) {
+                return null;
+            }
+
             final int windowCount = mWindowCache.size();
             if (windowCount > 0) {
                 // Careful to return the windows in a decreasing layer order.
@@ -280,12 +304,7 @@
             if (DEBUG) {
                 Log.i(LOG_TAG, "clear()");
             }
-            final int windowCount = mWindowCache.size();
-            for (int i = windowCount - 1; i >= 0; i--) {
-                AccessibilityWindowInfo window = mWindowCache.valueAt(i);
-                window.recycle();
-                mWindowCache.removeAt(i);
-            }
+            clearWindowCache();
             final int nodesForWindowCount = mNodeCache.size();
             for (int i = 0; i < nodesForWindowCount; i++) {
                 final int windowId = mNodeCache.keyAt(i);
@@ -297,6 +316,16 @@
         }
     }
 
+    private void clearWindowCache() {
+        final int windowCount = mWindowCache.size();
+        for (int i = windowCount - 1; i >= 0; i--) {
+            AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+            window.recycle();
+            mWindowCache.removeAt(i);
+        }
+        mIsAllWindowsCached = false;
+    }
+
     private void clearNodesForWindowLocked(int windowId) {
         if (DEBUG) {
             Log.i(LOG_TAG, "clearNodesForWindowLocked(" + windowId + ")");
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index b49cbc6..1406fbd 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -228,11 +228,7 @@
                 windows = connection.getWindows();
                 Binder.restoreCallingIdentity(identityToken);
                 if (windows != null) {
-                    final int windowCount = windows.size();
-                    for (int i = 0; i < windowCount; i++) {
-                        AccessibilityWindowInfo window = windows.get(i);
-                        sAccessibilityCache.addWindow(window);
-                    }
+                    sAccessibilityCache.setWindows(windows);
                     return windows;
                 }
             } else {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index f62b4cc..9442226 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1363,4 +1363,50 @@
      * offscreen but attached to a window.
      */
     public abstract boolean getOffscreenPreRaster();
+
+    /**
+     * Disables the action mode menu items according to {@code menuItems} flag.
+     * @param menuItems an integer field flag for the menu items to be disabled.
+     */
+    public abstract void setDisabledActionModeMenuItems(int menuItems);
+
+    /**
+     * Gets the action mode menu items that are disabled, expressed in an integer field flag.
+     * The default value is {@link #MENU_ITEM_NONE}
+     *
+     * @return all the disabled menu item flags combined with OR.
+     */
+    public abstract int getDisabledActionModeMenuItems();
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * No menu items should be disabled.
+     */
+    public static final int MENU_ITEM_NONE = 0;
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * Disable menu item "Share".
+     */
+    public static final int MENU_ITEM_SHARE = 1 << 0;
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * Disable menu item "Web Search".
+     */
+    public static final int MENU_ITEM_WEB_SEARCH = 1 << 1;
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * Disable all the action mode menu items for text processing.
+     * By default WebView searches for activities that are able to handle
+     * {@link android.content.Intent#ACTION_PROCESS_TEXT} and show them in the
+     * action mode menu. If this flag is set via {@link
+     * #setDisabledActionModeMenuItems}, these menu items will be disabled.
+     */
+    public static final int MENU_ITEM_PROCESS_TEXT = 1 << 2;
 }
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index a105b40..18d7470 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -21,7 +21,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.TypedArray;
-import android.os.Handler;
 import android.os.Message;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -112,7 +111,7 @@
         // home screen. Therefore, we register the receiver as the current
         // user not the one the context is for.
         getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
-                filter, null, mHandler);
+                filter, null, getHandler());
 
 
         if (mAutoStart) {
@@ -194,9 +193,8 @@
        // if the flipper is currently flipping automatically, and showNext() is called
        // we should we should make sure to reset the timer
        if (mRunning) {
-           mHandler.removeMessages(FLIP_MSG);
-           Message msg = mHandler.obtainMessage(FLIP_MSG);
-           mHandler.sendMessageDelayed(msg, mFlipInterval);
+           removeCallbacks(mFlipRunnable);
+           postDelayed(mFlipRunnable, mFlipInterval);
        }
        super.showNext();
    }
@@ -210,9 +208,8 @@
        // if the flipper is currently flipping automatically, and showPrevious() is called
        // we should we should make sure to reset the timer
        if (mRunning) {
-           mHandler.removeMessages(FLIP_MSG);
-           Message msg = mHandler.obtainMessage(FLIP_MSG);
-           mHandler.sendMessageDelayed(msg, mFlipInterval);
+           removeCallbacks(mFlipRunnable);
+           postDelayed(mFlipRunnable, mFlipInterval);
        }
        super.showPrevious();
    }
@@ -241,10 +238,9 @@
         if (running != mRunning) {
             if (running) {
                 showOnly(mWhichChild, flipNow);
-                Message msg = mHandler.obtainMessage(FLIP_MSG);
-                mHandler.sendMessageDelayed(msg, mFlipInterval);
+                postDelayed(mFlipRunnable, mFlipInterval);
             } else {
-                mHandler.removeMessages(FLIP_MSG);
+                removeCallbacks(mFlipRunnable);
             }
             mRunning = running;
         }
@@ -277,15 +273,11 @@
         return mAutoStart;
     }
 
-    private final int FLIP_MSG = 1;
-
-    private final Handler mHandler = new Handler() {
+    private final Runnable mFlipRunnable = new Runnable() {
         @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == FLIP_MSG) {
-                if (mRunning) {
-                    showNext();
-                }
+        public void run() {
+            if (mRunning) {
+                showNext();
             }
         }
     };
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 45eee34..7f5e2133 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -56,7 +56,6 @@
 
     private boolean mAttached;
 
-    private final Handler mHandler = new Handler();
     private float mMinutes;
     private float mHour;
     private boolean mChanged;
@@ -121,7 +120,7 @@
             // home screen. Therefore, we register the receiver as the current
             // user not the one the context is for.
             getContext().registerReceiverAsUser(mIntentReceiver,
-                    android.os.Process.myUserHandle(), filter, null, mHandler);
+                    android.os.Process.myUserHandle(), filter, null, getHandler());
         }
 
         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index ebb54ff..4d707e3 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -73,8 +73,6 @@
     private OnChronometerTickListener mOnChronometerTickListener;
     private StringBuilder mRecycle = new StringBuilder(8);
     
-    private static final int TICK_WHAT = 2;
-    
     /**
      * Initialize this Chronometer object.
      * Sets the base to the current time.
@@ -259,20 +257,21 @@
             if (running) {
                 updateText(SystemClock.elapsedRealtime());
                 dispatchChronometerTick();
-                mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), 1000);
+                postDelayed(mTickRunnable, 1000);
             } else {
-                mHandler.removeMessages(TICK_WHAT);
+                removeCallbacks(mTickRunnable);
             }
             mRunning = running;
         }
     }
-    
-    private Handler mHandler = new Handler() {
-        public void handleMessage(Message m) {
+
+    private final Runnable mTickRunnable = new Runnable() {
+        @Override
+        public void run() {
             if (mRunning) {
                 updateText(SystemClock.elapsedRealtime());
                 dispatchChronometerTick();
-                sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
+                postDelayed(mTickRunnable, 1000);
             }
         }
     };
diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java
index 9e442f9..1df1643 100644
--- a/core/java/android/widget/DigitalClock.java
+++ b/core/java/android/widget/DigitalClock.java
@@ -60,18 +60,18 @@
         if (mCalendar == null) {
             mCalendar = Calendar.getInstance();
         }
-
-        mFormatChangeObserver = new FormatChangeObserver();
-        getContext().getContentResolver().registerContentObserver(
-                Settings.System.CONTENT_URI, true, mFormatChangeObserver);
-
-        setFormat();
     }
 
     @Override
     protected void onAttachedToWindow() {
         mTickerStopped = false;
         super.onAttachedToWindow();
+
+        mFormatChangeObserver = new FormatChangeObserver();
+        getContext().getContentResolver().registerContentObserver(
+                Settings.System.CONTENT_URI, true, mFormatChangeObserver);
+        setFormat();
+
         mHandler = new Handler();
 
         /**
@@ -95,6 +95,8 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mTickerStopped = true;
+        getContext().getContentResolver().unregisterContentObserver(
+                mFormatChangeObserver);
     }
 
     private void setFormat() {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f53aa38..2fabe33 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -16,12 +16,6 @@
 
 package android.widget;
 
-import java.text.BreakIterator;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-
 import android.R;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -45,7 +39,6 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.ParcelableParcel;
@@ -113,6 +106,12 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.EditableInputConnection;
 
+import java.text.BreakIterator;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
 
 /**
  * Helper class used by TextView to handle editable text views.
@@ -392,7 +391,7 @@
 
         mTextView.removeCallbacks(mShowFloatingToolbar);
 
-        destroyDisplayListsData();
+        discardTextDisplayLists();
 
         if (mSpellChecker != null) {
             mSpellChecker.closeSession();
@@ -408,13 +407,13 @@
         mTemporaryDetach = false;
     }
 
-    private void destroyDisplayListsData() {
+    private void discardTextDisplayLists() {
         if (mTextRenderNodes != null) {
             for (int i = 0; i < mTextRenderNodes.length; i++) {
                 RenderNode displayList = mTextRenderNodes[i] != null
                         ? mTextRenderNodes[i].renderNode : null;
                 if (displayList != null && displayList.isValid()) {
-                    displayList.destroyDisplayListData();
+                    displayList.discardDisplayList();
                 }
             }
         }
@@ -1005,8 +1004,7 @@
         }
 
         if (!handled && mTextActionMode != null) {
-            // TODO: Fix dragging in extracted mode.
-            if (touchPositionIsInSelection() && !mTextView.isInExtractedMode()) {
+            if (touchPositionIsInSelection()) {
                 // Start a drag
                 final int start = mTextView.getSelectionStart();
                 final int end = mTextView.getSelectionEnd();
@@ -2072,14 +2070,14 @@
         if (shouldBlink()) {
             mShowCursor = SystemClock.uptimeMillis();
             if (mBlink == null) mBlink = new Blink();
-            mBlink.removeCallbacks(mBlink);
-            mBlink.postAtTime(mBlink, mShowCursor + BLINK);
+            mTextView.removeCallbacks(mBlink);
+            mTextView.postDelayed(mBlink, BLINK);
         } else {
-            if (mBlink != null) mBlink.removeCallbacks(mBlink);
+            if (mBlink != null) mTextView.removeCallbacks(mBlink);
         }
     }
 
-    private class Blink extends Handler implements Runnable {
+    private class Blink implements Runnable {
         private boolean mCancelled;
 
         public void run() {
@@ -2087,20 +2085,20 @@
                 return;
             }
 
-            removeCallbacks(Blink.this);
+            mTextView.removeCallbacks(this);
 
             if (shouldBlink()) {
                 if (mTextView.getLayout() != null) {
                     mTextView.invalidateCursorPath();
                 }
 
-                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
+                mTextView.postDelayed(this, BLINK);
             }
         }
 
         void cancel() {
             if (!mCancelled) {
-                removeCallbacks(Blink.this);
+                mTextView.removeCallbacks(this);
                 mCancelled = true;
             }
         }
@@ -4262,10 +4260,14 @@
             positionAtCursorOffset(offset, false);
         }
 
+        /**
+         * @param offset Cursor offset. Must be in [-1, length].
+         * @param parentScrolled If the parent has been scrolled or not.
+         */
         @Override
         protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
             super.positionAtCursorOffset(offset, parentScrolled);
-            mInWord = !getWordIteratorWithText().isBoundary(offset);
+            mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
         }
 
         @Override
@@ -4498,10 +4500,14 @@
             positionAtCursorOffset(offset, false);
         }
 
+        /**
+         * @param offset Cursor offset. Must be in [-1, length].
+         * @param parentScrolled If the parent has been scrolled or not.
+         */
         @Override
         protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
             super.positionAtCursorOffset(offset, parentScrolled);
-            mInWord = !getWordIteratorWithText().isBoundary(offset);
+            mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
         }
 
         @Override
@@ -4868,9 +4874,8 @@
                         mEndHandle.showAtLocation(endOffset);
 
                         // No longer the first dragging motion, reset.
-                        if (!(mTextView.isInExtractedMode())) {
-                            startSelectionActionMode();
-                        }
+                        startSelectionActionMode();
+
                         mDragAcceleratorActive = false;
                         mStartOffset = -1;
                         mSwitchedLines = false;
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index b187c1c..9ebbe36 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -201,9 +201,6 @@
 
     public Gallery(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        
-        mGestureDetector = new GestureDetector(context, this);
-        mGestureDetector.setIsLongpressEnabled(true);
 
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.Gallery, defStyleAttr, defStyleRes);
@@ -236,6 +233,16 @@
         mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (mGestureDetector == null) {
+            mGestureDetector = new GestureDetector(getContext(), this);
+            mGestureDetector.setIsLongpressEnabled(true);
+        }
+    }
+
     /**
      * Whether or not to callback on any {@link #getOnItemSelectedListener()}
      * while the items are being flinged. If false, only the final selected item
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 245c7332..6e90baf 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -232,6 +232,8 @@
                 if (w != mDrawableWidth || h != mDrawableHeight) {
                     mDrawableWidth = w;
                     mDrawableHeight = h;
+                    // updates the matrix, which is dependent on the bounds
+                    configureBounds();
                 }
             }
             /* we invalidate the whole view in this case because it's very
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index ff2f22b..8008637 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -20,8 +20,6 @@
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.media.AudioManager;
-import android.os.Handler;
-import android.os.Message;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
@@ -83,8 +81,6 @@
     private boolean mShowing;
     private boolean mDragging;
     private static final int sDefaultTimeout = 3000;
-    private static final int FADE_OUT = 1;
-    private static final int SHOW_PROGRESS = 2;
     private final boolean mUseFastForward;
     private boolean mFromXml;
     private boolean mListenersSet;
@@ -373,12 +369,11 @@
         // cause the progress bar to be updated even if mShowing
         // was already true.  This happens, for example, if we're
         // paused with the progress bar showing the user hits play.
-        mHandler.sendEmptyMessage(SHOW_PROGRESS);
+        post(mShowProgress);
 
         if (timeout != 0 && !mAccessibilityManager.isTouchExplorationEnabled()) {
-            mHandler.removeMessages(FADE_OUT);
-            Message msg = mHandler.obtainMessage(FADE_OUT);
-            mHandler.sendMessageDelayed(msg, timeout);
+            removeCallbacks(mFadeOut);
+            postDelayed(mFadeOut, timeout);
         }
     }
 
@@ -395,7 +390,7 @@
 
         if (mShowing) {
             try {
-                mHandler.removeMessages(SHOW_PROGRESS);
+                removeCallbacks(mShowProgress);
                 mWindowManager.removeView(mDecor);
             } catch (IllegalArgumentException ex) {
                 Log.w("MediaController", "already removed");
@@ -404,21 +399,19 @@
         }
     }
 
-    private final Handler mHandler = new Handler() {
+    private final Runnable mFadeOut = new Runnable() {
         @Override
-        public void handleMessage(Message msg) {
-            int pos;
-            switch (msg.what) {
-                case FADE_OUT:
-                    hide();
-                    break;
-                case SHOW_PROGRESS:
-                    pos = setProgress();
-                    if (!mDragging && mShowing && mPlayer.isPlaying()) {
-                        msg = obtainMessage(SHOW_PROGRESS);
-                        sendMessageDelayed(msg, 1000 - (pos % 1000));
-                    }
-                    break;
+        public void run() {
+            hide();
+        }
+    };
+
+    private final Runnable mShowProgress = new Runnable() {
+        @Override
+        public void run() {
+            int pos = setProgress();
+            if (!mDragging && mShowing && mPlayer.isPlaying()) {
+                postDelayed(mShowProgress, 1000 - (pos % 1000));
             }
         }
     };
@@ -587,7 +580,7 @@
             // the seekbar and b) once the user is done dragging the thumb
             // we will post one of these messages to the queue again and
             // this ensures that there will be exactly one message queued up.
-            mHandler.removeMessages(SHOW_PROGRESS);
+            removeCallbacks(mShowProgress);
         }
 
         @Override
@@ -615,7 +608,7 @@
             // Ensure that progress is properly updated in the future,
             // the call to show() does not guarantee this because it is a
             // no-op if we are already showing.
-            mHandler.sendEmptyMessage(SHOW_PROGRESS);
+            post(mShowProgress);
         }
     };
 
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index e241d4c..8c15cde 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -96,10 +96,16 @@
                 com.android.internal.R.styleable.Theme_quickContactBadgeOverlay);
         styledAttributes.recycle();
 
+        setOnClickListener(this);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
         if (!isInEditMode()) {
             mQueryHandler = new QueryHandler(mContext.getContentResolver());
         }
-        setOnClickListener(this);
     }
 
     @Override
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index 9c44236..a55e77d 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -93,7 +93,6 @@
     private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
     private static final float MAXIMUM_ACCELERATION = 2000.0f;
     private static final int VELOCITY_UNITS = 1000;
-    private static final int MSG_ANIMATE = 1000;
     private static final int ANIMATION_FRAME_DURATION = 1000 / 60;
 
     private static final int EXPANDED_FULL_OPEN = -10001;
@@ -123,7 +122,6 @@
     private OnDrawerCloseListener mOnDrawerCloseListener;
     private OnDrawerScrollListener mOnDrawerScrollListener;
 
-    private final Handler mHandler = new SlidingHandler();
     private float mAnimatedAcceleration;
     private float mAnimatedVelocity;
     private float mAnimationPosition;
@@ -553,8 +551,8 @@
         mAnimationLastTime = now;
         mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
         mAnimating = true;
-        mHandler.removeMessages(MSG_ANIMATE);
-        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
+        removeCallbacks(mSlidingRunnable);
+        postDelayed(mSlidingRunnable, ANIMATION_FRAME_DURATION);
         stopTracking();
     }
 
@@ -569,7 +567,7 @@
                     (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
             moveHandle((int) mAnimationPosition);
             mAnimating = true;
-            mHandler.removeMessages(MSG_ANIMATE);
+            removeCallbacks(mSlidingRunnable);
             long now = SystemClock.uptimeMillis();
             mAnimationLastTime = now;
             mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
@@ -577,7 +575,7 @@
         } else {
             if (mAnimating) {
                 mAnimating = false;
-                mHandler.removeMessages(MSG_ANIMATE);
+                removeCallbacks(mSlidingRunnable);
             }
             moveHandle(position);
         }
@@ -709,8 +707,7 @@
             } else {
                 moveHandle((int) mAnimationPosition);
                 mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
-                        mCurrentAnimationTime);
+                postDelayed(mSlidingRunnable, ANIMATION_FRAME_DURATION);
             }
         }
     }
@@ -974,13 +971,10 @@
         }
     }
 
-    private class SlidingHandler extends Handler {
-        public void handleMessage(Message m) {
-            switch (m.what) {
-                case MSG_ANIMATE:
-                    doAnimation();
-                    break;
-            }
+    private final Runnable mSlidingRunnable = new Runnable() {
+        @Override
+        public void run() {
+            doAnimation();
         }
-    }
+    };
 }
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index bcde315..ff10287 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -137,7 +137,13 @@
 
     private boolean mShowCurrentUserTime;
 
-    private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
+    private ContentObserver mFormatChangeObserver;
+    private class FormatChangeObserver extends ContentObserver {
+
+        public FormatChangeObserver(Handler handler) {
+            super(handler);
+        }
+
         @Override
         public void onChange(boolean selfChange) {
             chooseFormat();
@@ -553,13 +559,18 @@
     }
 
     private void registerObserver() {
-        final ContentResolver resolver = getContext().getContentResolver();
-        if (mShowCurrentUserTime) {
-            resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
-                    mFormatChangeObserver, UserHandle.USER_ALL);
-        } else {
-            resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
-                    mFormatChangeObserver);
+        if (isAttachedToWindow()) {
+            if (mFormatChangeObserver == null) {
+                mFormatChangeObserver = new FormatChangeObserver(getHandler());
+            }
+            final ContentResolver resolver = getContext().getContentResolver();
+            if (mShowCurrentUserTime) {
+                resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
+                        mFormatChangeObserver, UserHandle.USER_ALL);
+            } else {
+                resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
+                        mFormatChangeObserver);
+            }
         }
     }
 
@@ -568,8 +579,10 @@
     }
 
     private void unregisterObserver() {
-        final ContentResolver resolver = getContext().getContentResolver();
-        resolver.unregisterContentObserver(mFormatChangeObserver);
+        if (mFormatChangeObserver != null) {
+            final ContentResolver resolver = getContext().getContentResolver();
+            resolver.unregisterContentObserver(mFormatChangeObserver);
+        }
     }
 
     private void onTimeChanged() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0712052..13b1c4b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
 import android.R;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
@@ -116,15 +118,16 @@
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.PointerIcon;
 import android.view.View;
-import android.view.ViewParent;
-import android.view.ViewStructure;
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.view.ViewGroup.LayoutParams;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
 import android.view.ViewHierarchyEncoder;
+import android.view.ViewParent;
+import android.view.ViewRootImpl;
+import android.view.ViewStructure;
+import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -152,8 +155,6 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
 /**
  * Displays text to the user and optionally allows them to edit it.  A TextView
  * is a complete text editor, however the basic class is configured to not
@@ -1477,7 +1478,12 @@
                         }
                     }
                 }
+            } else if (mText instanceof Spannable) {
+                // Reset the selection.
+                stopTextActionMode();
+                Selection.setSelection((Spannable) mText, getSelectionStart(), getSelectionEnd());
             }
+
             if (mEditor.hasSelectionController()) {
                 mEditor.startSelectionActionMode();
             }
@@ -5281,14 +5287,6 @@
             mEditor.mCreatedWithASelection = false;
         }
 
-        // Phone specific code (there is no ExtractEditText on tablets).
-        // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
-        // not be set. Do the test here instead.
-        if (isInExtractedMode() && hasSelection() && mEditor != null
-                && mEditor.mTextActionMode == null && isShown() && hasWindowFocus()) {
-            mEditor.startSelectionActionMode();
-        }
-
         unregisterForPreDraw();
 
         return true;
@@ -5905,6 +5903,17 @@
         return mLayout != null ? mLayout.getHeight() : 0;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public int getPointerShape(MotionEvent event, float x, float y) {
+        if (isTextSelectable() || isTextEditable()) {
+            return PointerIcon.STYLE_TEXT;
+        }
+        return super.getPointerShape(event, x, y);
+    }
+
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 986c0f8..8e5af79 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -29,6 +29,8 @@
 
 import java.util.Locale;
 
+import libcore.icu.LocaleData;
+
 /**
  * A widget for selecting the time of day, in either 24-hour or AM/PM mode.
  * <p>
@@ -303,6 +305,16 @@
         void onValidationChanged(boolean valid);
     }
 
+    static String[] getAmPmStrings(Context context) {
+        final Locale locale = context.getResources().getConfiguration().locale;
+        final LocaleData d = LocaleData.get(locale);
+
+        final String[] result = new String[2];
+        result[0] = d.amPm[0].length() > 4 ? d.narrowAm : d.amPm[0];
+        result[1] = d.amPm[1].length() > 4 ? d.narrowPm : d.amPm[1];
+        return result;
+    }
+
     /**
      * An abstract class which can be used as a start for TimePicker implementations
      */
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 6d41c1d..4dc5fd3e 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -29,21 +29,22 @@
 import android.text.format.DateUtils;
 import android.text.style.TtsSpan;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.StateSet;
 import android.view.HapticFeedbackConstants;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.internal.R;
+import com.android.internal.widget.NumericTextView;
+import com.android.internal.widget.NumericTextView.OnValueChangedListener;
 
-import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Locale;
 
@@ -52,7 +53,12 @@
  */
 class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements
         RadialTimePickerView.OnValueSelectedListener {
-    private static final String TAG = "TimePickerClockDelegate";
+    /**
+     * Delay in milliseconds before valid but potentially incomplete, for
+     * example "1" but not "12", keyboard edits are propagated from the
+     * hour / minute fields to the radial picker.
+     */
+    private static final long DELAY_COMMIT_MILLIS = 2000;
 
     // Index used by RadialPickerLayout
     private static final int HOUR_INDEX = 0;
@@ -61,9 +67,6 @@
     // NOT a real index for the purpose of what's showing.
     private static final int AMPM_INDEX = 2;
 
-    // Also NOT a real index, just used for keyboard mode.
-    private static final int ENABLE_PICKER_INDEX = 3;
-
     private static final int[] ATTRS_TEXT_COLOR = new int[] {R.attr.textColor};
     private static final int[] ATTRS_DISABLED_ALPHA = new int[] {R.attr.disabledAlpha};
 
@@ -74,18 +77,14 @@
 
     private static final int HOURS_IN_HALF_DAY = 12;
 
-    private final View mHeaderView;
-    private final TextView mHourView;
-    private final TextView mMinuteView;
+    private final NumericTextView mHourView;
+    private final NumericTextView mMinuteView;
     private final View mAmPmLayout;
-    private final CheckedTextView mAmLabel;
-    private final CheckedTextView mPmLabel;
+    private final RadioButton mAmLabel;
+    private final RadioButton mPmLabel;
     private final RadialTimePickerView mRadialTimePickerView;
     private final TextView mSeparatorView;
 
-    private final String mAmText;
-    private final String mPmText;
-
     private boolean mIsEnabled = true;
     private boolean mAllowAutoAdvance;
     private int mInitialHourOfDay;
@@ -93,20 +92,14 @@
     private boolean mIs24HourView;
     private boolean mIsAmPmAtStart;
 
-    // For hardware IME input.
-    private char mPlaceholderText;
-    private String mDoublePlaceholderText;
-    private String mDeletedKeyFormat;
-    private boolean mInKbMode;
-    private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>();
-    private Node mLegalTimesTree;
-    private int mAmKeyCode;
-    private int mPmKeyCode;
-
     // Accessibility strings.
     private String mSelectHours;
     private String mSelectMinutes;
 
+    // Localization data.
+    private boolean mHourFormatShowLeadingZero;
+    private boolean mHourFormatStartsAtZero;
+
     // Most recent time announcement values for accessibility.
     private CharSequence mLastAnnouncedText;
     private boolean mLastAnnouncedIsHour;
@@ -127,43 +120,42 @@
         mSelectHours = res.getString(R.string.select_hours);
         mSelectMinutes = res.getString(R.string.select_minutes);
 
-        String[] amPmStrings = TimePickerSpinnerDelegate.getAmPmStrings(context);
-        mAmText = amPmStrings[0];
-        mPmText = amPmStrings[1];
-
         final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
                 R.layout.time_picker_material);
         final View mainView = inflater.inflate(layoutResourceId, delegator);
-
-        mHeaderView = mainView.findViewById(R.id.time_header);
+        final View headerView = mainView.findViewById(R.id.time_header);
+        headerView.setOnTouchListener(new NearestTouchDelegate());
 
         // Set up hour/minute labels.
-        mHourView = (TextView) mainView.findViewById(R.id.hours);
+        mHourView = (NumericTextView) mainView.findViewById(R.id.hours);
         mHourView.setOnClickListener(mClickListener);
+        mHourView.setOnFocusChangeListener(mFocusListener);
+        mHourView.setOnDigitEnteredListener(mDigitEnteredListener);
         mHourView.setAccessibilityDelegate(
                 new ClickActionDelegate(context, R.string.select_hours));
         mSeparatorView = (TextView) mainView.findViewById(R.id.separator);
-        mMinuteView = (TextView) mainView.findViewById(R.id.minutes);
+        mMinuteView = (NumericTextView) mainView.findViewById(R.id.minutes);
         mMinuteView.setOnClickListener(mClickListener);
+        mMinuteView.setOnFocusChangeListener(mFocusListener);
+        mMinuteView.setOnDigitEnteredListener(mDigitEnteredListener);
         mMinuteView.setAccessibilityDelegate(
                 new ClickActionDelegate(context, R.string.select_minutes));
-
-        // Now that we have text appearances out of the way, make sure the hour
-        // and minute views are correctly sized.
-        mHourView.setMinWidth(computeStableWidth(mHourView, 24));
-        mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60));
-
-        final SpannableStringBuilder amLabel = new SpannableStringBuilder()
-                .append(amPmStrings[0], new TtsSpan.VerbatimBuilder(amPmStrings[0]).build(), 0);
+        mMinuteView.setRange(0, 59);
 
         // Set up AM/PM labels.
         mAmPmLayout = mainView.findViewById(R.id.ampm_layout);
-        mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
+        mAmPmLayout.setOnTouchListener(new NearestTouchDelegate());
+
+        final String[] amPmStrings = TimePicker.getAmPmStrings(context);
+        mAmLabel = (RadioButton) mAmPmLayout.findViewById(R.id.am_label);
         mAmLabel.setText(obtainVerbatim(amPmStrings[0]));
         mAmLabel.setOnClickListener(mClickListener);
-        mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label);
+        ensureMinimumTextWidth(mAmLabel);
+
+        mPmLabel = (RadioButton) mAmPmLayout.findViewById(R.id.pm_label);
         mPmLabel.setText(obtainVerbatim(amPmStrings[1]));
         mPmLabel.setOnClickListener(mClickListener);
+        ensureMinimumTextWidth(mPmLabel);
 
         // For the sake of backwards compatibility, attempt to extract the text
         // color from the header time text appearance. If it's set, we'll let
@@ -195,7 +187,7 @@
 
         // Set up header background, if available.
         if (a.hasValueOrEmpty(R.styleable.TimePicker_headerBackground)) {
-            mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
+            headerView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
         }
 
         a.recycle();
@@ -207,18 +199,66 @@
 
         mAllowAutoAdvance = true;
 
-        // Set up for keyboard mode.
-        mDoublePlaceholderText = res.getString(R.string.time_placeholder);
-        mDeletedKeyFormat = res.getString(R.string.deleted_key);
-        mPlaceholderText = mDoublePlaceholderText.charAt(0);
-        mAmKeyCode = mPmKeyCode = -1;
-        generateLegalTimesTree();
+        // Updates mHourFormat variables used below.
+        updateHourFormat(mCurrentLocale, mIs24HourView);
 
-        // Initialize with current time
-        final Calendar calendar = Calendar.getInstance(mCurrentLocale);
-        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
-        final int currentMinute = calendar.get(Calendar.MINUTE);
-        initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
+        // Update hour text field.
+        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+        final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
+        mHourView.setRange(minHour, maxHour);
+        mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
+
+        // Initialize with current time.
+        mTempCalendar = Calendar.getInstance(mCurrentLocale);
+        final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY);
+        final int currentMinute = mTempCalendar.get(Calendar.MINUTE);
+        initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX);
+    }
+
+    /**
+     * Ensures that a TextView is wide enough to contain its text without
+     * wrapping or clipping. Measures the specified view and sets the minimum
+     * width to the view's desired width.
+     *
+     * @param v the text view to measure
+     */
+    private static void ensureMinimumTextWidth(TextView v) {
+        v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        // Set both the TextView and the View version of minimum
+        // width because they are subtly different.
+        final int minWidth = v.getMeasuredWidth();
+        v.setMinWidth(minWidth);
+        v.setMinimumWidth(minWidth);
+    }
+
+    /**
+     * Determines how the hour should be formatted and updates member variables
+     * related to hour formatting.
+     *
+     * @param locale the locale in which the view is displayed
+     * @param is24Hour whether the view is in 24-hour (hour-of-day) mode
+     */
+    private void updateHourFormat(Locale locale, boolean is24Hour) {
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(
+                locale, is24Hour ? "Hm" : "hm");
+        final int lengthPattern = bestDateTimePattern.length();
+        boolean showLeadingZero = false;
+        char hourFormat = '\0';
+
+        for (int i = 0; i < lengthPattern; i++) {
+            final char c = bestDateTimePattern.charAt(i);
+            if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
+                hourFormat = c;
+                if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
+                    showLeadingZero = true;
+                }
+                break;
+            }
+        }
+
+        mHourFormatShowLeadingZero = showLeadingZero;
+        mHourFormatStartsAtZero = hourFormat == 'K' || hourFormat == 'H';
     }
 
     private static final CharSequence obtainVerbatim(String text) {
@@ -290,51 +330,24 @@
         }
     }
 
-    private int computeStableWidth(TextView v, int maxNumber) {
-        int maxWidth = 0;
-
-        for (int i = 0; i < maxNumber; i++) {
-            final String text = String.format("%02d", i);
-            v.setText(text);
-            v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-
-            final int width = v.getMeasuredWidth();
-            if (width > maxWidth) {
-                maxWidth = width;
-            }
-        }
-
-        return maxWidth;
-    }
-
     private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
         mInitialHourOfDay = hourOfDay;
         mInitialMinute = minute;
         mIs24HourView = is24HourView;
-        mInKbMode = false;
         updateUI(index);
     }
 
     private void setupListeners() {
-        mHeaderView.setOnKeyListener(mKeyListener);
-        mHeaderView.setOnFocusChangeListener(mFocusListener);
-        mHeaderView.setFocusable(true);
-
         mRadialTimePickerView.setOnValueSelectedListener(this);
     }
 
     private void updateUI(int index) {
-        // Update RadialPicker values
-        updateRadialPicker(index);
-        // Enable or disable the AM/PM view.
         updateHeaderAmPm();
-        // Update Hour and Minutes
         updateHeaderHour(mInitialHourOfDay, false);
-        // Update time separator
         updateHeaderSeparator();
-        // Update Minutes
         updateHeaderMinute(mInitialMinute, false);
-        // Invalidate everything
+        updateRadialPicker(index);
+
         mDelegator.invalidate();
     }
 
@@ -447,14 +460,11 @@
         if (is24HourView == mIs24HourView) {
             return;
         }
+
         mIs24HourView = is24HourView;
-        generateLegalTimesTree();
-        int hour = mRadialTimePickerView.getCurrentHour();
-        mInitialHourOfDay = hour;
-        updateHeaderHour(hour, false);
-        updateHeaderAmPm();
-        updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
-        mDelegator.invalidate();
+        mInitialHourOfDay = getCurrentHour();
+
+        updateUI(mRadialTimePickerView.getCurrentItemShowing());
     }
 
     /**
@@ -499,20 +509,14 @@
     @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
         return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
-                is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing());
+                is24HourView(), getCurrentItemShowing());
     }
 
     @Override
     public void onRestoreInstanceState(Parcelable state) {
-        SavedState ss = (SavedState) state;
-        setInKbMode(ss.inKbMode());
-        setTypedTimes(ss.getTypesTimes());
+        final SavedState ss = (SavedState) state;
         initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing());
         mRadialTimePickerView.invalidate();
-        if (mInKbMode) {
-            tryStartingKbMode(-1);
-            mHourView.invalidate();
-        }
     }
 
     @Override
@@ -543,33 +547,6 @@
     }
 
     /**
-     * Set whether in keyboard mode or not.
-     *
-     * @param inKbMode True means in keyboard mode.
-     */
-    private void setInKbMode(boolean inKbMode) {
-        mInKbMode = inKbMode;
-    }
-
-    /**
-     * @return true if in keyboard mode
-     */
-    private boolean inKbMode() {
-        return mInKbMode;
-    }
-
-    private void setTypedTimes(ArrayList<Integer> typeTimes) {
-        mTypedTimes = typeTimes;
-    }
-
-    /**
-     * @return an array of typed times
-     */
-    private ArrayList<Integer> getTypedTimes() {
-        return mTypedTimes;
-    }
-
-    /**
      * @return the index of the current item showing
      */
     private int getCurrentItemShowing() {
@@ -595,19 +572,14 @@
         private final int mHour;
         private final int mMinute;
         private final boolean mIs24HourMode;
-        private final boolean mInKbMode;
-        private final ArrayList<Integer> mTypedTimes;
         private final int mCurrentItemShowing;
 
         private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
-                           boolean isKbMode, ArrayList<Integer> typedTimes,
-                           int currentItemShowing) {
+                int currentItemShowing) {
             super(superState);
             mHour = hour;
             mMinute = minute;
             mIs24HourMode = is24HourMode;
-            mInKbMode = isKbMode;
-            mTypedTimes = typedTimes;
             mCurrentItemShowing = currentItemShowing;
         }
 
@@ -616,8 +588,6 @@
             mHour = in.readInt();
             mMinute = in.readInt();
             mIs24HourMode = (in.readInt() == 1);
-            mInKbMode = (in.readInt() == 1);
-            mTypedTimes = in.readArrayList(getClass().getClassLoader());
             mCurrentItemShowing = in.readInt();
         }
 
@@ -633,14 +603,6 @@
             return mIs24HourMode;
         }
 
-        public boolean inKbMode() {
-            return mInKbMode;
-        }
-
-        public ArrayList<Integer> getTypesTimes() {
-            return mTypedTimes;
-        }
-
         public int getCurrentItemShowing() {
             return mCurrentItemShowing;
         }
@@ -651,13 +613,11 @@
             dest.writeInt(mHour);
             dest.writeInt(mMinute);
             dest.writeInt(mIs24HourMode ? 1 : 0);
-            dest.writeInt(mInKbMode ? 1 : 0);
-            dest.writeList(mTypedTimes);
             dest.writeInt(mCurrentItemShowing);
         }
 
         @SuppressWarnings({"unused", "hiding"})
-        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
+        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
             public SavedState createFromParcel(Parcel in) {
                 return new SavedState(in);
             }
@@ -703,12 +663,6 @@
             case AMPM_INDEX:
                 updateAmPmLabelStates(newValue);
                 break;
-            case ENABLE_PICKER_INDEX:
-                if (!isTypedTimeFullyLegal()) {
-                    mTypedTimes.clear();
-                }
-                finishKbMode();
-                break;
         }
 
         if (mOnTimeChangedListener != null) {
@@ -716,61 +670,41 @@
         }
     }
 
-    private void updateHeaderHour(int value, boolean announce) {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
-                (mIs24HourView) ? "Hm" : "hm");
-        final int lengthPattern = bestDateTimePattern.length();
-        boolean hourWithTwoDigit = false;
-        char hourFormat = '\0';
-        // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
-        // the hour format that we found.
-        for (int i = 0; i < lengthPattern; i++) {
-            final char c = bestDateTimePattern.charAt(i);
-            if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
-                hourFormat = c;
-                if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
-                    hourWithTwoDigit = true;
-                }
-                break;
-            }
+    /**
+     * Converts hour-of-day (0-23) time into a localized hour number.
+     *
+     * @param hourOfDay the hour-of-day (0-23)
+     * @return a localized hour number
+     */
+    private int getLocalizedHour(int hourOfDay) {
+        if (!mIs24HourView) {
+            // Convert to hour-of-am-pm.
+            hourOfDay %= 12;
         }
-        final String format;
-        if (hourWithTwoDigit) {
-            format = "%02d";
-        } else {
-            format = "%d";
+
+        if (!mHourFormatStartsAtZero && hourOfDay == 0) {
+            // Convert to clock-hour (either of-day or of-am-pm).
+            hourOfDay = mIs24HourView ? 24 : 12;
         }
-        if (mIs24HourView) {
-            // 'k' means 1-24 hour
-            if (hourFormat == 'k' && value == 0) {
-                value = 24;
-            }
-        } else {
-            // 'K' means 0-11 hour
-            value = modulo12(value, hourFormat == 'K');
-        }
-        CharSequence text = String.format(format, value);
-        mHourView.setText(text);
+
+        return hourOfDay;
+    }
+
+    private void updateHeaderHour(int hourOfDay, boolean announce) {
+        final int localizedHour = getLocalizedHour(hourOfDay);
+        mHourView.setValue(localizedHour);
+
         if (announce) {
-            tryAnnounceForAccessibility(text, true);
+            tryAnnounceForAccessibility(mHourView.getText(), true);
         }
     }
 
-    private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
-        if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
-            // TODO: Find a better solution, potentially live regions?
-            mDelegator.announceForAccessibility(text);
-            mLastAnnouncedText = text;
-            mLastAnnouncedIsHour = isHour;
-        }
-    }
+    private void updateHeaderMinute(int minuteOfHour, boolean announce) {
+        mMinuteView.setValue(minuteOfHour);
 
-    private static int modulo12(int n, boolean startWithZero) {
-        int value = n % 12;
-        if (value == 0 && !startWithZero) {
-            value = 12;
+        if (announce) {
+            tryAnnounceForAccessibility(mMinuteView.getText(), false);
         }
-        return value;
     }
 
     /**
@@ -812,14 +746,12 @@
         return -1;
     }
 
-    private void updateHeaderMinute(int value, boolean announceForAccessibility) {
-        if (value == 60) {
-            value = 0;
-        }
-        final CharSequence text = String.format(mCurrentLocale, "%02d", value);
-        mMinuteView.setText(text);
-        if (announceForAccessibility) {
-            tryAnnounceForAccessibility(text, false);
+    private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
+        if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
+            // TODO: Find a better solution, potentially live regions?
+            mDelegator.announceForAccessibility(text);
+            mLastAnnouncedText = text;
+            mLastAnnouncedIsHour = isHour;
         }
     }
 
@@ -848,477 +780,82 @@
         mRadialTimePickerView.setAmOrPm(amOrPm);
     }
 
-    /**
-     * For keyboard mode, processes key events.
-     *
-     * @param keyCode the pressed key.
-     *
-     * @return true if the key was successfully processed, false otherwise.
-     */
-    private boolean processKeyUp(int keyCode) {
-        if (keyCode == KeyEvent.KEYCODE_DEL) {
-            if (mInKbMode) {
-                if (!mTypedTimes.isEmpty()) {
-                    int deleted = deleteLastTypedKey();
-                    String deletedKeyStr;
-                    if (deleted == getAmOrPmKeyCode(AM)) {
-                        deletedKeyStr = mAmText;
-                    } else if (deleted == getAmOrPmKeyCode(PM)) {
-                        deletedKeyStr = mPmText;
-                    } else {
-                        deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
+    private final OnValueChangedListener mDigitEnteredListener = new OnValueChangedListener() {
+        @Override
+        public void onValueChanged(NumericTextView view, int value,
+                boolean isValid, boolean isFinished) {
+            final Runnable commitCallback;
+            final View nextFocusTarget;
+            if (view == mHourView) {
+                commitCallback = mCommitHour;
+                nextFocusTarget = view.isFocused() ? mMinuteView : null;
+            } else if (view == mMinuteView) {
+                commitCallback = mCommitMinute;
+                nextFocusTarget = null;
+            } else {
+                return;
+            }
+
+            view.removeCallbacks(commitCallback);
+
+            if (isValid) {
+                if (isFinished) {
+                    // Done with hours entry, make visual updates
+                    // immediately and move to next focus if needed.
+                    commitCallback.run();
+
+                    if (nextFocusTarget != null) {
+                        nextFocusTarget.requestFocus();
                     }
-                    mDelegator.announceForAccessibility(
-                            String.format(mDeletedKeyFormat, deletedKeyStr));
-                    updateDisplay(true);
-                }
-            }
-        } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
-                || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
-                || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
-                || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
-                || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
-                || (!mIs24HourView &&
-                (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
-            if (!mInKbMode) {
-                if (mRadialTimePickerView == null) {
-                    // Something's wrong, because time picker should definitely not be null.
-                    Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
-                    return true;
-                }
-                mTypedTimes.clear();
-                tryStartingKbMode(keyCode);
-                return true;
-            }
-            // We're already in keyboard mode.
-            if (addKeyIfLegal(keyCode)) {
-                updateDisplay(false);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Try to start keyboard mode with the specified key.
-     *
-     * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
-     * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
-     * key.
-     */
-    private void tryStartingKbMode(int keyCode) {
-        if (keyCode == -1 || addKeyIfLegal(keyCode)) {
-            mInKbMode = true;
-            onValidationChanged(false);
-            updateDisplay(false);
-            mRadialTimePickerView.setInputEnabled(false);
-        }
-    }
-
-    private boolean addKeyIfLegal(int keyCode) {
-        // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
-        // we'll need to see if AM/PM have been typed.
-        if ((mIs24HourView && mTypedTimes.size() == 4) ||
-                (!mIs24HourView && isTypedTimeFullyLegal())) {
-            return false;
-        }
-
-        mTypedTimes.add(keyCode);
-        if (!isTypedTimeLegalSoFar()) {
-            deleteLastTypedKey();
-            return false;
-        }
-
-        int val = getValFromKeyCode(keyCode);
-        mDelegator.announceForAccessibility(String.format("%d", val));
-        // Automatically fill in 0's if AM or PM was legally entered.
-        if (isTypedTimeFullyLegal()) {
-            if (!mIs24HourView && mTypedTimes.size() <= 3) {
-                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
-                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
-            }
-            onValidationChanged(true);
-        }
-
-        return true;
-    }
-
-    /**
-     * Traverse the tree to see if the keys that have been typed so far are legal as is,
-     * or may become legal as more keys are typed (excluding backspace).
-     */
-    private boolean isTypedTimeLegalSoFar() {
-        Node node = mLegalTimesTree;
-        for (int keyCode : mTypedTimes) {
-            node = node.canReach(keyCode);
-            if (node == null) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Check if the time that has been typed so far is completely legal, as is.
-     */
-    private boolean isTypedTimeFullyLegal() {
-        if (mIs24HourView) {
-            // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
-            // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
-            int[] values = getEnteredTime(null);
-            return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
-        } else {
-            // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
-            // legally added at specific times based on the tree's algorithm.
-            return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
-                    mTypedTimes.contains(getAmOrPmKeyCode(PM)));
-        }
-    }
-
-    private int deleteLastTypedKey() {
-        int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
-        if (!isTypedTimeFullyLegal()) {
-            onValidationChanged(false);
-        }
-        return deleted;
-    }
-
-    /**
-     * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
-     */
-    private void finishKbMode() {
-        mInKbMode = false;
-        if (!mTypedTimes.isEmpty()) {
-            int values[] = getEnteredTime(null);
-            mRadialTimePickerView.setCurrentHour(values[0]);
-            mRadialTimePickerView.setCurrentMinute(values[1]);
-            if (!mIs24HourView) {
-                mRadialTimePickerView.setAmOrPm(values[2]);
-            }
-            mTypedTimes.clear();
-        }
-        updateDisplay(false);
-        mRadialTimePickerView.setInputEnabled(true);
-    }
-
-    /**
-     * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
-     * empty, either show an empty display (filled with the placeholder text), or update from the
-     * timepicker's values.
-     *
-     * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
-     * Otherwise, revert to the timepicker's values.
-     */
-    private void updateDisplay(boolean allowEmptyDisplay) {
-        if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
-            int hour = mRadialTimePickerView.getCurrentHour();
-            int minute = mRadialTimePickerView.getCurrentMinute();
-            updateHeaderHour(hour, false);
-            updateHeaderMinute(minute, false);
-            if (!mIs24HourView) {
-                updateAmPmLabelStates(hour < 12 ? AM : PM);
-            }
-            setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true);
-            onValidationChanged(true);
-        } else {
-            boolean[] enteredZeros = {false, false};
-            int[] values = getEnteredTime(enteredZeros);
-            String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
-            String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
-            String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
-                    String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
-            String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
-                    String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
-            mHourView.setText(hourStr);
-            mHourView.setActivated(false);
-            mMinuteView.setText(minuteStr);
-            mMinuteView.setActivated(false);
-            if (!mIs24HourView) {
-                updateAmPmLabelStates(values[2]);
-            }
-        }
-    }
-
-    private int getValFromKeyCode(int keyCode) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_0:
-                return 0;
-            case KeyEvent.KEYCODE_1:
-                return 1;
-            case KeyEvent.KEYCODE_2:
-                return 2;
-            case KeyEvent.KEYCODE_3:
-                return 3;
-            case KeyEvent.KEYCODE_4:
-                return 4;
-            case KeyEvent.KEYCODE_5:
-                return 5;
-            case KeyEvent.KEYCODE_6:
-                return 6;
-            case KeyEvent.KEYCODE_7:
-                return 7;
-            case KeyEvent.KEYCODE_8:
-                return 8;
-            case KeyEvent.KEYCODE_9:
-                return 9;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * Get the currently-entered time, as integer values of the hours and minutes typed.
-     *
-     * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
-     * may then be used for the caller to know whether zeros had been explicitly entered as either
-     * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
-     *
-     * @return A size-3 int array. The first value will be the hours, the second value will be the
-     * minutes, and the third will be either AM or PM.
-     */
-    private int[] getEnteredTime(boolean[] enteredZeros) {
-        int amOrPm = -1;
-        int startIndex = 1;
-        if (!mIs24HourView && isTypedTimeFullyLegal()) {
-            int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
-            if (keyCode == getAmOrPmKeyCode(AM)) {
-                amOrPm = AM;
-            } else if (keyCode == getAmOrPmKeyCode(PM)){
-                amOrPm = PM;
-            }
-            startIndex = 2;
-        }
-        int minute = -1;
-        int hour = -1;
-        for (int i = startIndex; i <= mTypedTimes.size(); i++) {
-            int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
-            if (i == startIndex) {
-                minute = val;
-            } else if (i == startIndex+1) {
-                minute += 10 * val;
-                if (enteredZeros != null && val == 0) {
-                    enteredZeros[1] = true;
-                }
-            } else if (i == startIndex+2) {
-                hour = val;
-            } else if (i == startIndex+3) {
-                hour += 10 * val;
-                if (enteredZeros != null && val == 0) {
-                    enteredZeros[0] = true;
+                } else {
+                    // May still be making changes. Postpone visual
+                    // updates to prevent distracting the user.
+                    view.postDelayed(commitCallback, DELAY_COMMIT_MILLIS);
                 }
             }
         }
+    };
 
-        return new int[] { hour, minute, amOrPm };
-    }
+    private final Runnable mCommitHour = new Runnable() {
+        @Override
+        public void run() {
+            setCurrentHour(mHourView.getValue());
+        }
+    };
 
-    /**
-     * Get the keycode value for AM and PM in the current language.
-     */
-    private int getAmOrPmKeyCode(int amOrPm) {
-        // Cache the codes.
-        if (mAmKeyCode == -1 || mPmKeyCode == -1) {
-            // Find the first character in the AM/PM text that is unique.
-            final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-            final CharSequence amText = mAmText.toLowerCase(mCurrentLocale);
-            final CharSequence pmText = mPmText.toLowerCase(mCurrentLocale);
-            final int N = Math.min(amText.length(), pmText.length());
-            for (int i = 0; i < N; i++) {
-                final char amChar = amText.charAt(i);
-                final char pmChar = pmText.charAt(i);
-                if (amChar != pmChar) {
-                    // There should be 4 events: a down and up for both AM and PM.
-                    final KeyEvent[] events = kcm.getEvents(new char[] { amChar, pmChar });
-                    if (events != null && events.length == 4) {
-                        mAmKeyCode = events[0].getKeyCode();
-                        mPmKeyCode = events[2].getKeyCode();
-                    } else {
-                        Log.e(TAG, "Unable to find keycodes for AM and PM.");
-                    }
-                    break;
+    private final Runnable mCommitMinute = new Runnable() {
+        @Override
+        public void run() {
+            setCurrentMinute(mMinuteView.getValue());
+        }
+    };
+
+    private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
+        @Override
+        public void onFocusChange(View v, boolean focused) {
+            if (focused) {
+                switch (v.getId()) {
+                    case R.id.am_label:
+                        setAmOrPm(AM);
+                        break;
+                    case R.id.pm_label:
+                        setAmOrPm(PM);
+                        break;
+                    case R.id.hours:
+                        setCurrentItemShowing(HOUR_INDEX, true, true);
+                        break;
+                    case R.id.minutes:
+                        setCurrentItemShowing(MINUTE_INDEX, true, true);
+                        break;
+                    default:
+                        // Failed to handle this click, don't vibrate.
+                        return;
                 }
+
+                tryVibrate();
             }
         }
-
-        if (amOrPm == AM) {
-            return mAmKeyCode;
-        } else if (amOrPm == PM) {
-            return mPmKeyCode;
-        }
-
-        return -1;
-    }
-
-    /**
-     * Create a tree for deciding what keys can legally be typed.
-     */
-    private void generateLegalTimesTree() {
-        // Create a quick cache of numbers to their keycodes.
-        final int k0 = KeyEvent.KEYCODE_0;
-        final int k1 = KeyEvent.KEYCODE_1;
-        final int k2 = KeyEvent.KEYCODE_2;
-        final int k3 = KeyEvent.KEYCODE_3;
-        final int k4 = KeyEvent.KEYCODE_4;
-        final int k5 = KeyEvent.KEYCODE_5;
-        final int k6 = KeyEvent.KEYCODE_6;
-        final int k7 = KeyEvent.KEYCODE_7;
-        final int k8 = KeyEvent.KEYCODE_8;
-        final int k9 = KeyEvent.KEYCODE_9;
-
-        // The root of the tree doesn't contain any numbers.
-        mLegalTimesTree = new Node();
-        if (mIs24HourView) {
-            // We'll be re-using these nodes, so we'll save them.
-            Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
-            Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            // The first digit must be followed by the second digit.
-            minuteFirstDigit.addChild(minuteSecondDigit);
-
-            // The first digit may be 0-1.
-            Node firstDigit = new Node(k0, k1);
-            mLegalTimesTree.addChild(firstDigit);
-
-            // When the first digit is 0-1, the second digit may be 0-5.
-            Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
-            firstDigit.addChild(secondDigit);
-            // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
-            secondDigit.addChild(minuteFirstDigit);
-
-            // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
-            Node thirdDigit = new Node(k6, k7, k8, k9);
-            // The time must now be finished. E.g. 0:55, 1:08.
-            secondDigit.addChild(thirdDigit);
-
-            // When the first digit is 0-1, the second digit may be 6-9.
-            secondDigit = new Node(k6, k7, k8, k9);
-            firstDigit.addChild(secondDigit);
-            // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
-            secondDigit.addChild(minuteFirstDigit);
-
-            // The first digit may be 2.
-            firstDigit = new Node(k2);
-            mLegalTimesTree.addChild(firstDigit);
-
-            // When the first digit is 2, the second digit may be 0-3.
-            secondDigit = new Node(k0, k1, k2, k3);
-            firstDigit.addChild(secondDigit);
-            // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
-            secondDigit.addChild(minuteFirstDigit);
-
-            // When the first digit is 2, the second digit may be 4-5.
-            secondDigit = new Node(k4, k5);
-            firstDigit.addChild(secondDigit);
-            // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
-            secondDigit.addChild(minuteSecondDigit);
-
-            // The first digit may be 3-9.
-            firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
-            mLegalTimesTree.addChild(firstDigit);
-            // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
-            firstDigit.addChild(minuteFirstDigit);
-        } else {
-            // We'll need to use the AM/PM node a lot.
-            // Set up AM and PM to respond to "a" and "p".
-            Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
-
-            // The first hour digit may be 1.
-            Node firstDigit = new Node(k1);
-            mLegalTimesTree.addChild(firstDigit);
-            // We'll allow quick input of on-the-hour times. E.g. 1pm.
-            firstDigit.addChild(ampm);
-
-            // When the first digit is 1, the second digit may be 0-2.
-            Node secondDigit = new Node(k0, k1, k2);
-            firstDigit.addChild(secondDigit);
-            // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
-            secondDigit.addChild(ampm);
-
-            // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
-            Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
-            secondDigit.addChild(thirdDigit);
-            // The time may be finished now. E.g. 1:02pm, 1:25am.
-            thirdDigit.addChild(ampm);
-
-            // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
-            // the fourth digit may be 0-9.
-            Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            thirdDigit.addChild(fourthDigit);
-            // The time must be finished now. E.g. 10:49am, 12:40pm.
-            fourthDigit.addChild(ampm);
-
-            // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
-            thirdDigit = new Node(k6, k7, k8, k9);
-            secondDigit.addChild(thirdDigit);
-            // The time must be finished now. E.g. 1:08am, 1:26pm.
-            thirdDigit.addChild(ampm);
-
-            // When the first digit is 1, the second digit may be 3-5.
-            secondDigit = new Node(k3, k4, k5);
-            firstDigit.addChild(secondDigit);
-
-            // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
-            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            secondDigit.addChild(thirdDigit);
-            // The time must be finished now. E.g. 1:39am, 1:50pm.
-            thirdDigit.addChild(ampm);
-
-            // The hour digit may be 2-9.
-            firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
-            mLegalTimesTree.addChild(firstDigit);
-            // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
-            firstDigit.addChild(ampm);
-
-            // When the first digit is 2-9, the second digit may be 0-5.
-            secondDigit = new Node(k0, k1, k2, k3, k4, k5);
-            firstDigit.addChild(secondDigit);
-
-            // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
-            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            secondDigit.addChild(thirdDigit);
-            // The time must be finished now. E.g. 2:57am, 9:30pm.
-            thirdDigit.addChild(ampm);
-        }
-    }
-
-    /**
-     * Simple node class to be used for traversal to check for legal times.
-     * mLegalKeys represents the keys that can be typed to get to the node.
-     * mChildren are the children that can be reached from this node.
-     */
-    private class Node {
-        private int[] mLegalKeys;
-        private ArrayList<Node> mChildren;
-
-        public Node(int... legalKeys) {
-            mLegalKeys = legalKeys;
-            mChildren = new ArrayList<Node>();
-        }
-
-        public void addChild(Node child) {
-            mChildren.add(child);
-        }
-
-        public boolean containsKey(int key) {
-            for (int i = 0; i < mLegalKeys.length; i++) {
-                if (mLegalKeys[i] == key) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        public Node canReach(int key) {
-            if (mChildren == null) {
-                return null;
-            }
-            for (Node child : mChildren) {
-                if (child.containsKey(key)) {
-                    return child;
-                }
-            }
-            return null;
-        }
-    }
+    };
 
     private final View.OnClickListener mClickListener = new View.OnClickListener() {
         @Override
@@ -1347,28 +884,55 @@
         }
     };
 
-    private final View.OnKeyListener mKeyListener = new View.OnKeyListener() {
-        @Override
-        public boolean onKey(View v, int keyCode, KeyEvent event) {
-            if (event.getAction() == KeyEvent.ACTION_UP) {
-                return processKeyUp(keyCode);
+    /**
+     * Delegates unhandled touches in a view group to the nearest child view.
+     */
+    private static class NearestTouchDelegate implements View.OnTouchListener {
+            private View mInitialTouchTarget;
+
+            @Override
+            public boolean onTouch(View view, MotionEvent motionEvent) {
+                final int actionMasked = motionEvent.getActionMasked();
+                if (actionMasked == MotionEvent.ACTION_DOWN) {
+                    mInitialTouchTarget = findNearestChild((ViewGroup) view,
+                            (int) motionEvent.getX(), (int) motionEvent.getY());
+                }
+
+                final View child = mInitialTouchTarget;
+                if (child == null) {
+                    return false;
+                }
+
+                final float offsetX = view.getScrollX() - child.getLeft();
+                final float offsetY = view.getScrollY() - child.getTop();
+                motionEvent.offsetLocation(offsetX, offsetY);
+                final boolean handled = child.dispatchTouchEvent(motionEvent);
+                motionEvent.offsetLocation(-offsetX, -offsetY);
+
+                if (actionMasked == MotionEvent.ACTION_UP
+                        || actionMasked == MotionEvent.ACTION_CANCEL) {
+                    mInitialTouchTarget = null;
+                }
+
+                return handled;
             }
-            return false;
-        }
-    };
 
-    private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
-        @Override
-        public void onFocusChange(View v, boolean hasFocus) {
-            if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) {
-                finishKbMode();
+        private View findNearestChild(ViewGroup v, int x, int y) {
+            View bestChild = null;
+            int bestDist = Integer.MAX_VALUE;
 
-                if (mOnTimeChangedListener != null) {
-                    mOnTimeChangedListener.onTimeChanged(mDelegator,
-                            mRadialTimePickerView.getCurrentHour(),
-                            mRadialTimePickerView.getCurrentMinute());
+            for (int i = 0, count = v.getChildCount(); i < count; i++) {
+                final View child = v.getChildAt(i);
+                final int dX = x - (child.getLeft() + child.getWidth() / 2);
+                final int dY = y - (child.getTop() + child.getHeight() / 2);
+                final int dist = dX * dX + dY * dY;
+                if (bestDist > dist) {
+                    bestChild = child;
+                    bestDist = dist;
                 }
             }
+
+            return bestChild;
         }
-    };
+    }
 }
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 94e7ba1..65af7aa 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -96,7 +96,7 @@
         // home screen. Therefore, we register the receiver as the current
         // user not the one the context is for.
         getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
-                filter, null, mHandler);
+                filter, null, getHandler());
 
         if (mAutoStart) {
             // Automatically start when requested
@@ -173,10 +173,9 @@
         if (running != mRunning) {
             if (running) {
                 showOnly(mWhichChild, flipNow);
-                Message msg = mHandler.obtainMessage(FLIP_MSG);
-                mHandler.sendMessageDelayed(msg, mFlipInterval);
+                postDelayed(mFlipRunnable, mFlipInterval);
             } else {
-                mHandler.removeMessages(FLIP_MSG);
+                removeCallbacks(mFlipRunnable);
             }
             mRunning = running;
         }
@@ -209,17 +208,12 @@
         return mAutoStart;
     }
 
-    private final int FLIP_MSG = 1;
-
-    private final Handler mHandler = new Handler() {
+    private final Runnable mFlipRunnable = new Runnable() {
         @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == FLIP_MSG) {
-                if (mRunning) {
-                    showNext();
-                    msg = obtainMessage(FLIP_MSG);
-                    sendMessageDelayed(msg, mFlipInterval);
-                }
+        public void run() {
+            if (mRunning) {
+                showNext();
+                postDelayed(mFlipRunnable, mFlipInterval);
             }
         }
     };
diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java
index 0255bdb..7d4f8ed 100644
--- a/core/java/android/widget/ZoomButton.java
+++ b/core/java/android/widget/ZoomButton.java
@@ -17,7 +17,6 @@
 package android.widget;
 
 import android.content.Context;
-import android.os.Handler;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -26,12 +25,11 @@
 
 public class ZoomButton extends ImageButton implements OnLongClickListener {
 
-    private final Handler mHandler;
     private final Runnable mRunnable = new Runnable() {
         public void run() {
             if (hasOnClickListeners() && mIsInLongpress && isEnabled()) {
                 callOnClick();
-                mHandler.postDelayed(this, mZoomSpeed);
+                postDelayed(this, mZoomSpeed);
             }
         }
     };
@@ -53,7 +51,6 @@
 
     public ZoomButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mHandler = new Handler();
         setOnLongClickListener(this);
     }
 
@@ -72,7 +69,7 @@
 
     public boolean onLongClick(View v) {
         mIsInLongpress = true;
-        mHandler.post(mRunnable);
+        post(mRunnable);
         return true;
     }
         
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f462a47..b7b7400 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -373,6 +373,11 @@
         int targetsToQuery = 0;
         for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
+            if (adapter.getScore(dri) == 0) {
+                // A score of 0 means the app hasn't been used in some time;
+                // don't query it as it's not likely to be relevant.
+                continue;
+            }
             final ActivityInfo ai = dri.getResolveInfo().activityInfo;
             final Bundle md = ai.metaData;
             final String serviceName = md != null ? convertServiceName(ai.packageName,
@@ -920,7 +925,7 @@
         @Override
         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
             // Descending order
-            return (int) Math.signum(lhs.getScore() - rhs.getScore());
+            return (int) Math.signum(rhs.getScore() - lhs.getScore());
         }
     }
 
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 3cddbf6..3b9b8db 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -117,7 +117,7 @@
     void noteWifiRadioPowerState(int powerState, long timestampNs);
     void noteNetworkInterfaceType(String iface, int type);
     void noteNetworkStatsEnabled();
-    void noteDeviceIdleMode(boolean enabled, String activeReason, int activeUid);
+    void noteDeviceIdleMode(int mode, String activeReason, int activeUid);
     void setBatteryState(int status, int health, int plugType, int level, int temp, int volt);
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java
index b90cb36..6cc8ba9 100644
--- a/core/java/com/android/internal/logging/MetricsConstants.java
+++ b/core/java/com/android/internal/logging/MetricsConstants.java
@@ -262,6 +262,24 @@
     public static final int TUNER_DEMO_MODE_ON = 236;
     public static final int TUNER_BATTERY_PERCENTAGE = 237;
     public static final int FUELGAUGE_INACTIVE_APPS = 238;
+    public static final int ACTION_ASSIST_LONG_PRESS = 239;
+    public static final int FINGERPRINT_ENROLLING = 240;
+    public static final int FINGERPRINT_FIND_SENSOR = 241;
+    public static final int FINGERPRINT_ENROLL_FINISH = 242;
+    public static final int FINGERPRINT_ENROLL_INTRO = 243;
+    public static final int FINGERPRINT_ENROLL_ONBOARD = 244;
+    public static final int FINGERPRINT_ENROLL_SIDECAR = 245;
+    public static final int FINGERPRINT_ENROLLING_SETUP = 246;
+    public static final int FINGERPRINT_FIND_SENSOR_SETUP = 247;
+    public static final int FINGERPRINT_ENROLL_FINISH_SETUP = 248;
+    public static final int FINGERPRINT_ENROLL_INTRO_SETUP = 249;
+    public static final int FINGERPRINT_ENROLL_ONBOARD_SETUP = 250;
+    public static final int ACTION_FINGERPRINT_ENROLL = 251;
+    public static final int ACTION_FINGERPRINT_AUTH = 252;
+    public static final int ACTION_FINGERPRINT_DELETE = 253;
+    public static final int ACTION_FINGERPRINT_RENAME = 254;
+    public static final int ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE = 255;
+    public static final int ACTION_WIGGLE_CAMERA_GESTURE = 256;
 
     // These constants must match those in the analytic pipeline, do not edit.
     // Add temporary values to the top of MetricsLogger instead.
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index c25db65..2c5e50c 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -27,25 +27,6 @@
  */
 public class MetricsLogger implements MetricsConstants {
     // Temporary constants go here, to await migration to MetricsConstants.
-    // next value is 239;
-    public static final int ACTION_ASSIST_LONG_PRESS = 239;
-    public static final int FINGERPRINT_ENROLLING = 240;
-    public static final int FINGERPRINT_FIND_SENSOR = 241;
-    public static final int FINGERPRINT_ENROLL_FINISH = 242;
-    public static final int FINGERPRINT_ENROLL_INTRO = 243;
-    public static final int FINGERPRINT_ENROLL_ONBOARD = 244;
-    public static final int FINGERPRINT_ENROLL_SIDECAR = 245;
-    public static final int FINGERPRINT_ENROLLING_SETUP = 246;
-    public static final int FINGERPRINT_FIND_SENSOR_SETUP = 247;
-    public static final int FINGERPRINT_ENROLL_FINISH_SETUP = 248;
-    public static final int FINGERPRINT_ENROLL_INTRO_SETUP = 249;
-    public static final int FINGERPRINT_ENROLL_ONBOARD_SETUP = 250;
-    public static final int ACTION_FINGERPRINT_ENROLL = 251;
-    public static final int ACTION_FINGERPRINT_AUTH = 252;
-    public static final int ACTION_FINGERPRINT_DELETE = 253;
-    public static final int ACTION_FINGERPRINT_RENAME = 254;
-    public static final int ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE = 255;
-    public static final int ACTION_WIGGLE_CAMERA_GESTURE = 256;
     public static final int QS_LOCK_TILE = 257;
     public static final int QS_USER_TILE = 258;
     public static final int QS_BATTERY_TILE = 259;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 64b7768..f73df00 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -105,7 +105,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 132 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 135 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -338,8 +338,15 @@
     boolean mDeviceIdling;
     StopwatchTimer mDeviceIdlingTimer;
 
-    boolean mDeviceIdleModeEnabled;
-    StopwatchTimer mDeviceIdleModeEnabledTimer;
+    boolean mDeviceLightIdling;
+    StopwatchTimer mDeviceLightIdlingTimer;
+
+    int mDeviceIdleMode;
+    long mLastIdleTimeStart;
+    long mLongestLightIdleTime;
+    long mLongestFullIdleTime;
+    StopwatchTimer mDeviceIdleModeLightTimer;
+    StopwatchTimer mDeviceIdleModeFullTimer;
 
     boolean mPhoneOn;
     StopwatchTimer mPhoneOnTimer;
@@ -3219,42 +3226,69 @@
         }
     }
 
-    public void noteDeviceIdleModeLocked(boolean enabled, String activeReason, int activeUid) {
+    public void noteDeviceIdleModeLocked(int mode, String activeReason, int activeUid) {
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
-        boolean nowIdling = enabled;
-        if (mDeviceIdling && !enabled && activeReason == null) {
+        boolean nowIdling = mode == DEVICE_IDLE_MODE_FULL;
+        if (mDeviceIdling && !nowIdling && activeReason == null) {
             // We don't go out of general idling mode until explicitly taken out of
             // device idle through going active or significant motion.
             nowIdling = true;
         }
+        boolean nowLightIdling = mode == DEVICE_IDLE_MODE_LIGHT;
+        if (mDeviceLightIdling && !nowLightIdling && !nowIdling && activeReason == null) {
+            // We don't go out of general light idling mode until explicitly taken out of
+            // device idle through going active or significant motion.
+            nowLightIdling = true;
+        }
+        if (activeReason != null && (mDeviceIdling || mDeviceLightIdling)) {
+            addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ACTIVE,
+                    activeReason, activeUid);
+        }
         if (mDeviceIdling != nowIdling) {
             mDeviceIdling = nowIdling;
             int stepState = nowIdling ? STEP_LEVEL_MODE_DEVICE_IDLE : 0;
             mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_DEVICE_IDLE) ^ stepState;
             mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_DEVICE_IDLE) | stepState;
-            if (enabled) {
+            if (nowIdling) {
                 mDeviceIdlingTimer.startRunningLocked(elapsedRealtime);
             } else {
                 mDeviceIdlingTimer.stopRunningLocked(elapsedRealtime);
             }
         }
-        if (mDeviceIdleModeEnabled != enabled) {
-            mDeviceIdleModeEnabled = enabled;
-            addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ACTIVE,
-                    activeReason != null ? activeReason : "", activeUid);
-            if (enabled) {
-                mHistoryCur.states2 |= HistoryItem.STATE2_DEVICE_IDLE_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode enabled to: "
-                        + Integer.toHexString(mHistoryCur.states2));
-                mDeviceIdleModeEnabledTimer.startRunningLocked(elapsedRealtime);
+        if (mDeviceLightIdling != nowLightIdling) {
+            mDeviceLightIdling = nowLightIdling;
+            if (nowLightIdling) {
+                mDeviceLightIdlingTimer.startRunningLocked(elapsedRealtime);
             } else {
-                mHistoryCur.states2 &= ~HistoryItem.STATE2_DEVICE_IDLE_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode disabled to: "
-                        + Integer.toHexString(mHistoryCur.states2));
-                mDeviceIdleModeEnabledTimer.stopRunningLocked(elapsedRealtime);
+                mDeviceLightIdlingTimer.stopRunningLocked(elapsedRealtime);
             }
+        }
+        if (mDeviceIdleMode != mode) {
+            mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
+                    | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode changed to: "
+                    + Integer.toHexString(mHistoryCur.states2));
             addHistoryRecordLocked(elapsedRealtime, uptime);
+            long lastDuration = elapsedRealtime - mLastIdleTimeStart;
+            mLastIdleTimeStart = elapsedRealtime;
+            if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+                if (lastDuration > mLongestLightIdleTime) {
+                    mLongestLightIdleTime = lastDuration;
+                }
+                mDeviceIdleModeLightTimer.stopRunningLocked(elapsedRealtime);
+            } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_FULL) {
+                if (lastDuration > mLongestFullIdleTime) {
+                    mLongestFullIdleTime = lastDuration;
+                }
+                mDeviceIdleModeFullTimer.stopRunningLocked(elapsedRealtime);
+            }
+            if (mode == DEVICE_IDLE_MODE_LIGHT) {
+                mDeviceIdleModeLightTimer.startRunningLocked(elapsedRealtime);
+            } else if (mode == DEVICE_IDLE_MODE_FULL) {
+                mDeviceIdleModeFullTimer.startRunningLocked(elapsedRealtime);
+            }
+            mDeviceIdleMode = mode;
         }
     }
 
@@ -4140,20 +4174,55 @@
         return mPowerSaveModeEnabledTimer.getCountLocked(which);
     }
 
-    @Override public long getDeviceIdleModeEnabledTime(long elapsedRealtimeUs, int which) {
-        return mDeviceIdleModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    @Override public long getDeviceIdleModeTime(int mode, long elapsedRealtimeUs,
+            int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceIdleModeLightTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+            case DEVICE_IDLE_MODE_FULL:
+                return mDeviceIdleModeFullTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+        return 0;
     }
 
-    @Override public int getDeviceIdleModeEnabledCount(int which) {
-        return mDeviceIdleModeEnabledTimer.getCountLocked(which);
+    @Override public int getDeviceIdleModeCount(int mode, int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceIdleModeLightTimer.getCountLocked(which);
+            case DEVICE_IDLE_MODE_FULL:
+                return mDeviceIdleModeFullTimer.getCountLocked(which);
+        }
+        return 0;
     }
 
-    @Override public long getDeviceIdlingTime(long elapsedRealtimeUs, int which) {
-        return mDeviceIdlingTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    @Override public long getLongestDeviceIdleModeTime(int mode) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mLongestLightIdleTime;
+            case DEVICE_IDLE_MODE_FULL:
+                return mLongestFullIdleTime;
+        }
+        return 0;
     }
 
-    @Override public int getDeviceIdlingCount(int which) {
-        return mDeviceIdlingTimer.getCountLocked(which);
+    @Override public long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceLightIdlingTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+            case DEVICE_IDLE_MODE_FULL:
+                return mDeviceIdlingTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+        return 0;
+    }
+
+    @Override public int getDeviceIdlingCount(int mode, int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceLightIdlingTimer.getCountLocked(which);
+            case DEVICE_IDLE_MODE_FULL:
+                return mDeviceIdlingTimer.getCountLocked(which);
+        }
+        return 0;
     }
 
     @Override public int getNumConnectivityChange(int which) {
@@ -6855,7 +6924,9 @@
         }
         mInteractiveTimer = new StopwatchTimer(null, -10, null, mOnBatteryTimeBase);
         mPowerSaveModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase);
-        mDeviceIdleModeEnabledTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase);
+        mDeviceIdleModeLightTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase);
+        mDeviceIdleModeFullTimer = new StopwatchTimer(null, -14, null, mOnBatteryTimeBase);
+        mDeviceLightIdlingTimer = new StopwatchTimer(null, -15, null, mOnBatteryTimeBase);
         mDeviceIdlingTimer = new StopwatchTimer(null, -12, null, mOnBatteryTimeBase);
         mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
@@ -7484,7 +7555,11 @@
         }
         mInteractiveTimer.reset(false);
         mPowerSaveModeEnabledTimer.reset(false);
-        mDeviceIdleModeEnabledTimer.reset(false);
+        mLongestLightIdleTime = 0;
+        mLongestFullIdleTime = 0;
+        mDeviceIdleModeLightTimer.reset(false);
+        mDeviceIdleModeFullTimer.reset(false);
+        mDeviceLightIdlingTimer.reset(false);
         mDeviceIdlingTimer.reset(false);
         mPhoneOnTimer.reset(false);
         mAudioOnTimer.reset(false);
@@ -9224,7 +9299,11 @@
         mInteractiveTimer.readSummaryFromParcelLocked(in);
         mPhoneOn = false;
         mPowerSaveModeEnabledTimer.readSummaryFromParcelLocked(in);
-        mDeviceIdleModeEnabledTimer.readSummaryFromParcelLocked(in);
+        mLongestLightIdleTime = in.readLong();
+        mLongestFullIdleTime = in.readLong();
+        mDeviceIdleModeLightTimer.readSummaryFromParcelLocked(in);
+        mDeviceIdleModeFullTimer.readSummaryFromParcelLocked(in);
+        mDeviceLightIdlingTimer.readSummaryFromParcelLocked(in);
         mDeviceIdlingTimer.readSummaryFromParcelLocked(in);
         mPhoneOnTimer.readSummaryFromParcelLocked(in);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
@@ -9558,7 +9637,11 @@
         }
         mInteractiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         mPowerSaveModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
-        mDeviceIdleModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        out.writeLong(mLongestLightIdleTime);
+        out.writeLong(mLongestFullIdleTime);
+        mDeviceIdleModeLightTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mDeviceIdleModeFullTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mDeviceLightIdlingTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         mDeviceIdlingTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
@@ -9892,7 +9975,11 @@
         mInteractiveTimer = new StopwatchTimer(null, -10, null, mOnBatteryTimeBase, in);
         mPhoneOn = false;
         mPowerSaveModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
-        mDeviceIdleModeEnabledTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase, in);
+        mLongestLightIdleTime = in.readLong();
+        mLongestFullIdleTime = in.readLong();
+        mDeviceIdleModeLightTimer = new StopwatchTimer(null, -14, null, mOnBatteryTimeBase, in);
+        mDeviceIdleModeFullTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase, in);
+        mDeviceLightIdlingTimer = new StopwatchTimer(null, -15, null, mOnBatteryTimeBase, in);
         mDeviceIdlingTimer = new StopwatchTimer(null, -12, null, mOnBatteryTimeBase, in);
         mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase, in);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
@@ -10053,7 +10140,11 @@
         }
         mInteractiveTimer.writeToParcel(out, uSecRealtime);
         mPowerSaveModeEnabledTimer.writeToParcel(out, uSecRealtime);
-        mDeviceIdleModeEnabledTimer.writeToParcel(out, uSecRealtime);
+        out.writeLong(mLongestLightIdleTime);
+        out.writeLong(mLongestFullIdleTime);
+        mDeviceIdleModeLightTimer.writeToParcel(out, uSecRealtime);
+        mDeviceIdleModeFullTimer.writeToParcel(out, uSecRealtime);
+        mDeviceLightIdlingTimer.writeToParcel(out, uSecRealtime);
         mDeviceIdlingTimer.writeToParcel(out, uSecRealtime);
         mPhoneOnTimer.writeToParcel(out, uSecRealtime);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
@@ -10188,8 +10279,12 @@
             mInteractiveTimer.logState(pr, "  ");
             pr.println("*** Power save mode timer:");
             mPowerSaveModeEnabledTimer.logState(pr, "  ");
-            pr.println("*** Device idle mode timer:");
-            mDeviceIdleModeEnabledTimer.logState(pr, "  ");
+            pr.println("*** Device idle mode light timer:");
+            mDeviceIdleModeLightTimer.logState(pr, "  ");
+            pr.println("*** Device idle mode full timer:");
+            mDeviceIdleModeFullTimer.logState(pr, "  ");
+            pr.println("*** Device light idling timer:");
+            mDeviceLightIdlingTimer.logState(pr, "  ");
             pr.println("*** Device idling timer:");
             mDeviceIdlingTimer.logState(pr, "  ");
             pr.println("*** Phone timer:");
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index aaa89df..9a5cde6 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -70,6 +70,7 @@
     private static final String TAG = "Zygote";
 
     private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
+    private static final String PROPERTY_RUNNING_IN_CONTAINER = "ro.boot.container";
 
     private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
 
@@ -503,7 +504,6 @@
     private static boolean startSystemServer(String abiList, String socketName)
             throws MethodAndArgsCaller, RuntimeException {
         long capabilities = posixCapabilitiesAsBits(
-            OsConstants.CAP_BLOCK_SUSPEND,
             OsConstants.CAP_KILL,
             OsConstants.CAP_NET_ADMIN,
             OsConstants.CAP_NET_BIND_SERVICE,
@@ -515,6 +515,10 @@
             OsConstants.CAP_SYS_TIME,
             OsConstants.CAP_SYS_TTY_CONFIG
         );
+        /* Containers run without this capability, so avoid setting it in that case */
+        if (!SystemProperties.getBoolean(PROPERTY_RUNNING_IN_CONTAINER, false)) {
+            capabilities |= posixCapabilitiesAsBits(OsConstants.CAP_BLOCK_SUSPEND);
+        }
         /* Hardcoded command line to start the system server */
         String args[] = {
             "--setuid=1000",
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 75ad916a..4644211 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.FIRST_DYNAMIC_STACK_ID;
 import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
 import static android.app.ActivityManager.INVALID_STACK_ID;
 import static android.view.View.MeasureSpec.AT_MOST;
 import static android.view.View.MeasureSpec.EXACTLY;
@@ -32,8 +31,6 @@
 import android.animation.ObjectAnimator;
 import android.app.ActivityManagerNative;
 import android.app.SearchManager;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
 import android.os.Build;
 import android.os.UserHandle;
 
@@ -3177,16 +3174,16 @@
                 int vis = show ? VISIBLE : INVISIBLE;
                 visibilityChanged = state.targetVisibility != vis;
                 state.targetVisibility = vis;
+                LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                if (lp.height != resolvedHeight || lp.width != resolvedWidth
+                        || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
+                    lp.height = resolvedHeight;
+                    lp.width = resolvedWidth;
+                    lp.gravity = resolvedGravity;
+                    lp.rightMargin = rightMargin;
+                    view.setLayoutParams(lp);
+                }
                 if (show) {
-                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                    if (lp.height != resolvedHeight || lp.width != resolvedWidth
-                            || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
-                        lp.height = resolvedHeight;
-                        lp.width = resolvedWidth;
-                        lp.gravity = resolvedGravity;
-                        lp.rightMargin = rightMargin;
-                        view.setLayoutParams(lp);
-                    }
                     view.setBackgroundColor(color);
                 }
             }
@@ -4228,7 +4225,8 @@
                 }
             }
             nonClientDecorView.setPhoneWindow(this, hasNonClientDecor(mWorkspaceId),
-                    nonClientDecorHasShadow(mWorkspaceId));
+                    nonClientDecorHasShadow(mWorkspaceId), getResizingBackgroundDrawable(),
+                    mDecor.getContext().getDrawable(R.drawable.non_client_decor_title_focused));
         }
         // Tell the decor if it has a visible non client decor.
         mDecor.enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor(mWorkspaceId));
@@ -5409,7 +5407,7 @@
      * @param workspaceId The Id of the workspace which contains this window.
      * @Return Returns true if the window should show a non client decor.
      **/
-    private boolean hasNonClientDecor(int workspaceId) {
+    private static boolean hasNonClientDecor(int workspaceId) {
         return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
     }
 
@@ -5432,4 +5430,31 @@
             }
         }
     }
+
+    /**
+     * Returns the color used to fill areas the app has not rendered content to yet when the user
+     * is resizing the window of an activity in multi-window mode.
+     * */
+    private Drawable getResizingBackgroundDrawable() {
+        final Context context = mDecor.getContext();
+
+        if (mBackgroundResource != 0) {
+            final Drawable drawable = context.getDrawable(mBackgroundResource);
+            if (drawable != null) {
+                return drawable;
+            }
+        }
+
+        if (mBackgroundFallbackResource != 0) {
+            final Drawable fallbackDrawable = context.getDrawable(mBackgroundFallbackResource);
+            if (fallbackDrawable != null) {
+                return fallbackDrawable;
+            }
+        }
+
+        // We shouldn't really get here as the background fallback should be always available since
+        // it is defaulted by the system.
+        Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + this);
+        return null;
+    }
 }
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index ed7492a..5d502c9 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.AttributeSet;
@@ -52,7 +53,7 @@
  * <li>The resize handles which allow to resize the window.</li>
  * </ul>
  * After creating the view, the function
- * {@link #setPhoneWindow(PhoneWindow owner, boolean windowHasShadow)} needs to be called to make
+ * {@link #setPhoneWindow} needs to be called to make
  * the connection to it's owning PhoneWindow.
  * Note: At this time the application can change various attributes of the DecorView which
  * will break things (in settle/unexpected ways):
@@ -97,6 +98,9 @@
     // The resize frame renderer.
     private ResizeFrameThread mFrameRendererThread = null;
 
+    private Drawable mResizingBackgroundDrawable;
+    private Drawable mCaptionBackgroundDrawable;
+
     public NonClientDecorView(Context context) {
         super(context);
     }
@@ -133,10 +137,13 @@
         }
     }
 
-    public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
+    public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow,
+            Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawableDrawable) {
         mOwner = owner;
         mWindowHasShadow = windowHasShadow;
         mShowDecor = showDecor;
+        mResizingBackgroundDrawable = resizingBackgroundDrawable;
+        mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable;
         updateCaptionVisibility();
         if (mWindowHasShadow) {
             initializeElevation();
@@ -624,7 +631,8 @@
             // barely show while the entire screen is moving.
             mFrameNode.setLeftTopRightBottom(left, top, left + width, top + mLastCaptionHeight);
             DisplayListCanvas canvas = mFrameNode.start(width, height);
-            canvas.drawColor(Color.BLACK);
+            mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
+            mCaptionBackgroundDrawable.draw(canvas);
             mFrameNode.end(canvas);
 
             mBackdropNode.setLeftTopRightBottom(left, top + mLastCaptionHeight, left + width,
@@ -632,9 +640,8 @@
 
             // The backdrop: clear everything with the background. Clipping is done elsewhere.
             canvas = mBackdropNode.start(width, height - mLastCaptionHeight);
-            // TODO(skuhne): mOwner.getDecorView().mBackgroundFallback.draw(..) - or similar.
-            // Note: This might not work (calculator for example uses a transparent background).
-            canvas.drawColor(0xff808080);
+            mResizingBackgroundDrawable.setBounds(0, 0, left + width, top + height);
+            mResizingBackgroundDrawable.draw(canvas);
             mBackdropNode.end(canvas);
 
             // We need to render both rendered nodes explicitly.
diff --git a/core/java/com/android/internal/widget/NumericTextView.java b/core/java/com/android/internal/widget/NumericTextView.java
new file mode 100644
index 0000000..27c5834
--- /dev/null
+++ b/core/java/com/android/internal/widget/NumericTextView.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.StateSet;
+import android.view.KeyEvent;
+import android.widget.TextView;
+
+/**
+ * Extension of TextView that can handle displaying and inputting a range of
+ * numbers.
+ * <p>
+ * Clients of this view should never call {@link #setText(CharSequence)} or
+ * {@link #setHint(CharSequence)} directly. Instead, they should call
+ * {@link #setValue(int)} to modify the currently displayed value.
+ */
+public class NumericTextView extends TextView {
+    private static final int RADIX = 10;
+    private static final double LOG_RADIX = Math.log(RADIX);
+
+    private int mMinValue = 0;
+    private int mMaxValue = 99;
+
+    /** Number of digits in the maximum value. */
+    private int mMaxCount = 2;
+
+    private boolean mShowLeadingZeroes = true;
+
+    private int mValue;
+
+    /** Number of digits entered during editing mode. */
+    private int mCount;
+
+    /** Used to restore the value after an aborted edit. */
+    private int mPreviousValue;
+
+    private OnValueChangedListener mListener;
+
+    public NumericTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Generate the hint text color based on disabled state.
+        final int textColorDisabled = getTextColors().getColorForState(StateSet.get(0), 0);
+        setHintTextColor(textColorDisabled);
+
+        setFocusable(true);
+    }
+
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+
+        if (focused) {
+            mPreviousValue = mValue;
+            mValue = 0;
+            mCount = 0;
+
+            // Transfer current text to hint.
+            setHint(getText());
+            setText("");
+        } else {
+            if (mCount == 0) {
+                // No digits were entered, revert to previous value.
+                mValue = mPreviousValue;
+
+                setText(getHint());
+                setHint("");
+            }
+
+            // Ensure the committed value is within range.
+            if (mValue < mMinValue) {
+                mValue = mMinValue;
+            }
+
+            setValue(mValue);
+
+            if (mListener != null) {
+                mListener.onValueChanged(this, mValue, true, true);
+            }
+        }
+    }
+
+    /**
+     * Sets the currently displayed value.
+     * <p>
+     * The specified {@code value} must be within the range specified by
+     * {@link #setRange(int, int)} (e.g. between {@link #getRangeMinimum()}
+     * and {@link #getRangeMaximum()}).
+     *
+     * @param value the value to display
+     */
+    public final void setValue(int value) {
+        if (mValue != value) {
+            mValue = value;
+
+            updateDisplayedValue();
+        }
+    }
+
+    /**
+     * Returns the currently displayed value.
+     * <p>
+     * If the value is currently being edited, returns the live value which may
+     * not be within the range specified by {@link #setRange(int, int)}.
+     *
+     * @return the currently displayed value
+     */
+    public final int getValue() {
+        return mValue;
+    }
+
+    /**
+     * Sets the valid range (inclusive).
+     *
+     * @param minValue the minimum valid value (inclusive)
+     * @param maxValue the maximum valid value (inclusive)
+     */
+    public final void setRange(int minValue, int maxValue) {
+        if (mMinValue != minValue) {
+            mMinValue = minValue;
+        }
+
+        if (mMaxValue != maxValue) {
+            mMaxValue = maxValue;
+            mMaxCount = 1 + (int) (Math.log(maxValue) / LOG_RADIX);
+
+            updateMinimumWidth();
+            updateDisplayedValue();
+        }
+    }
+
+    /**
+     * @return the minimum value value (inclusive)
+     */
+    public final int getRangeMinimum() {
+        return mMinValue;
+    }
+
+    /**
+     * @return the maximum value value (inclusive)
+     */
+    public final int getRangeMaximum() {
+        return mMaxValue;
+    }
+
+    /**
+     * Sets whether this view shows leading zeroes.
+     * <p>
+     * When leading zeroes are shown, the displayed value will be padded
+     * with zeroes to the width of the maximum value as specified by
+     * {@link #setRange(int, int)} (see also {@link #getRangeMaximum()}.
+     * <p>
+     * For example, with leading zeroes shown, a maximum of 99 and value of
+     * 9 would display "09". A maximum of 100 and a value of 9 would display
+     * "009". With leading zeroes hidden, both cases would show "9".
+     *
+     * @param showLeadingZeroes {@code true} to show leading zeroes,
+     *                          {@code false} to hide them
+     */
+    public final void setShowLeadingZeroes(boolean showLeadingZeroes) {
+        if (mShowLeadingZeroes != showLeadingZeroes) {
+            mShowLeadingZeroes = showLeadingZeroes;
+
+            updateDisplayedValue();
+        }
+    }
+
+    public final boolean getShowLeadingZeroes() {
+        return mShowLeadingZeroes;
+    }
+
+    /**
+     * Computes the display value and updates the text of the view.
+     * <p>
+     * This method should be called whenever the current value or display
+     * properties (leading zeroes, max digits) change.
+     */
+    private void updateDisplayedValue() {
+        final String format;
+        if (mShowLeadingZeroes) {
+            format = "%0" + mMaxCount + "d";
+        } else {
+            format = "%d";
+        }
+
+        // Always use String.format() rather than Integer.toString()
+        // to obtain correctly localized values.
+        setText(String.format(format, mValue));
+    }
+
+    /**
+     * Computes the minimum width in pixels required to display all possible
+     * values and updates the minimum width of the view.
+     * <p>
+     * This method should be called whenever the maximum value changes.
+     */
+    private void updateMinimumWidth() {
+        final CharSequence previousText = getText();
+        int maxWidth = 0;
+
+        for (int i = 0; i < mMaxValue; i++) {
+            setText(String.format("%0" + mMaxCount + "d", i));
+            measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+            final int width = getMeasuredWidth();
+            if (width > maxWidth) {
+                maxWidth = width;
+            }
+        }
+
+        setText(previousText);
+        setMinWidth(maxWidth);
+        setMinimumWidth(maxWidth);
+    }
+
+    public final void setOnDigitEnteredListener(OnValueChangedListener listener) {
+        mListener = listener;
+    }
+
+    public final OnValueChangedListener getOnDigitEnteredListener() {
+        return mListener;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return isKeyCodeNumeric(keyCode)
+                || (keyCode == KeyEvent.KEYCODE_DEL)
+                || super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return isKeyCodeNumeric(keyCode)
+                || (keyCode == KeyEvent.KEYCODE_DEL)
+                || super.onKeyMultiple(keyCode, repeatCount, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return handleKeyUp(keyCode)
+                || super.onKeyUp(keyCode, event);
+    }
+
+    private boolean handleKeyUp(int keyCode) {
+        if (keyCode == KeyEvent.KEYCODE_DEL) {
+            // Backspace removes the least-significant digit, if available.
+            if (mCount > 0) {
+                mValue /= RADIX;
+                mCount--;
+            }
+        } else if (isKeyCodeNumeric(keyCode)) {
+            if (mCount < mMaxCount) {
+                final int keyValue = numericKeyCodeToInt(keyCode);
+                final int newValue = mValue * RADIX + keyValue;
+                if (newValue <= mMaxValue) {
+                    mValue = newValue;
+                    mCount++;
+                }
+            }
+        } else {
+            return false;
+        }
+
+        final String formattedValue;
+        if (mCount > 0) {
+            // If the user types 01, we should always show the leading 0 even if
+            // getShowLeadingZeroes() is false. Preserve typed leading zeroes by
+            // using the number of digits entered as the format width.
+            formattedValue = String.format("%0" + mCount + "d", mValue);
+        } else {
+            formattedValue = "";
+        }
+
+        setText(formattedValue);
+
+        if (mListener != null) {
+            final boolean isValid = mValue >= mMinValue;
+            final boolean isFinished = mCount >= mMaxCount || mValue * RADIX > mMaxValue;
+            mListener.onValueChanged(this, mValue, isValid, isFinished);
+        }
+
+        return true;
+    }
+
+    private static boolean isKeyCodeNumeric(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
+                || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
+                || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
+                || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
+                || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9;
+    }
+
+    private static int numericKeyCodeToInt(int keyCode) {
+        return keyCode - KeyEvent.KEYCODE_0;
+    }
+
+    public interface OnValueChangedListener {
+        /**
+         * Called when the value displayed by {@code view} changes.
+         *
+         * @param view the view whose value changed
+         * @param value the new value
+         * @param isValid {@code true} if the value is valid (e.g. within the
+         *                range specified by {@link #setRange(int, int)}),
+         *                {@code false} otherwise
+         * @param isFinished {@code true} if the no more digits may be entered,
+         *                   {@code false} if more digits may be entered
+         */
+        void onValueChanged(NumericTextView view, int value, boolean isValid, boolean isFinished);
+    }
+}
diff --git a/core/jni/android/graphics/MinikinSkia.cpp b/core/jni/android/graphics/MinikinSkia.cpp
index 4649b07..8ac5d46 100644
--- a/core/jni/android/graphics/MinikinSkia.cpp
+++ b/core/jni/android/graphics/MinikinSkia.cpp
@@ -32,16 +32,6 @@
     SkSafeUnref(mTypeface);
 }
 
-bool MinikinFontSkia::GetGlyph(uint32_t codepoint, uint32_t *glyph) const {
-    SkPaint paint;
-    paint.setTypeface(mTypeface);
-    paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);
-    uint16_t glyph16;
-    paint.textToGlyphs(&codepoint, sizeof(codepoint), &glyph16);
-    *glyph  = glyph16;
-    return !!glyph16;
-}
-
 static void MinikinFontSkia_SetSkiaPaint(const MinikinFont* font, SkPaint* skPaint, const MinikinPaint& paint) {
     skPaint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
     skPaint->setTextSize(paint.size);
diff --git a/core/jni/android/graphics/MinikinSkia.h b/core/jni/android/graphics/MinikinSkia.h
index a06428d..8f469ba 100644
--- a/core/jni/android/graphics/MinikinSkia.h
+++ b/core/jni/android/graphics/MinikinSkia.h
@@ -28,8 +28,6 @@
 
     ~MinikinFontSkia();
 
-    bool GetGlyph(uint32_t codepoint, uint32_t *glyph) const;
-
     float GetHorizontalAdvance(uint32_t glyph_id,
         const MinikinPaint &paint) const;
 
@@ -54,4 +52,4 @@
 
 }  // namespace android
 
-#endif  // _ANDROID_GRAPHICS_MINIKIN_SKIA_H_
\ No newline at end of file
+#endif  // _ANDROID_GRAPHICS_MINIKIN_SKIA_H_
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index d1780d6..7fd288a 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -33,11 +33,11 @@
     FontStyle resolved = resolvedFace->fStyle;
 
     /* Prepare minikin FontStyle */
-    const std::string& lang = paint->getTextLocale();
-    FontLanguage minikinLang(lang.c_str(), lang.size());
+    const std::string& langs = paint->getTextLocales();
+    FontLanguages minikinLangs(langs.c_str(), langs.size());
     FontVariant minikinVariant = (paint->getFontVariant() == VARIANT_ELEGANT) ? VARIANT_ELEGANT
             : VARIANT_COMPACT;
-    FontStyle minikinStyle(minikinLang, minikinVariant, resolved.getWeight(), resolved.getItalic());
+    FontStyle minikinStyle(minikinLangs, minikinVariant, resolved.getWeight(), resolved.getItalic());
 
     /* Prepare minikin Paint */
     // Note: it would be nice to handle fractional size values (it would improve smooth zoom
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 314e4b6..b50046f 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -28,6 +28,7 @@
 #include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
 #include "SkMaskFilter.h"
+#include "SkPath.h"
 #include "SkRasterizer.h"
 #include "SkShader.h"
 #include "SkTypeface.h"
@@ -43,12 +44,10 @@
 #include "Paint.h"
 #include "TypefaceImpl.h"
 
-#include <vector>
+#include <cassert>
+#include <cstring>
 #include <memory>
-
-// temporary for debugging
-#include <Caches.h>
-#include <utils/Log.h>
+#include <vector>
 
 namespace android {
 
@@ -71,12 +70,12 @@
     paint->setTextEncoding(Paint::kGlyphID_TextEncoding);
 }
 
-struct LocaleCacheEntry {
-    std::string javaLocale;
-    std::string languageTag;
+struct LocalesCacheEntry {
+    std::string javaLocales;
+    std::string languageTags;
 };
 
-static thread_local LocaleCacheEntry sSingleEntryLocaleCache;
+static thread_local LocalesCacheEntry sSingleEntryLocalesCache;
 
 namespace PaintGlue {
     enum MoveOpt {
@@ -360,17 +359,57 @@
         output[0] = '\0';
     }
 
-    static void setTextLocale(JNIEnv* env, jobject clazz, jlong objHandle, jstring locale) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        ScopedUtfChars localeChars(env, locale);
-        if (sSingleEntryLocaleCache.javaLocale != localeChars.c_str()) {
-            sSingleEntryLocaleCache.javaLocale = localeChars.c_str();
-            char langTag[ULOC_FULLNAME_CAPACITY];
-            toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeChars.c_str());
-            sSingleEntryLocaleCache.languageTag = langTag;
+    static void toLanguageTags(std::string* output, const char* locales) {
+        if (output == NULL) {
+            return;
+        }
+        if (locales == NULL) {
+            output->clear();
+            return;
         }
 
-        obj->setTextLocale(sSingleEntryLocaleCache.languageTag);
+        char langTag[ULOC_FULLNAME_CAPACITY];
+        const char* commaLoc = strchr(locales, ',');
+        if (commaLoc == NULL) {
+            assert(locales[0] != '\0');  // the string should not be empty
+            toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locales);
+            *output = langTag;
+            return;
+        }
+
+        size_t len = strlen(locales);
+        char locale[len];
+        output->clear();
+        output->reserve(len);
+        const char* lastStart = locales;
+        do {
+            assert(lastStart > commaLoc);  // the substring should not be empty
+            strncpy(locale, lastStart, commaLoc - lastStart);
+            locale[commaLoc - lastStart] = '\0';
+            toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
+            if (langTag[0] != '\0') {
+                output->append(langTag);
+                output->push_back(',');
+            }
+            lastStart = commaLoc + 1;
+            commaLoc = strchr(lastStart, ',');
+        } while (commaLoc != NULL);
+        assert(lastStart[0] != '\0');  // the final substring should not be empty
+        toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, lastStart);
+        if (langTag[0] != '\0') {
+            output->append(langTag);
+        }
+    }
+
+    static void setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
+        Paint* obj = reinterpret_cast<Paint*>(objHandle);
+        ScopedUtfChars localesChars(env, locales);
+        if (sSingleEntryLocalesCache.javaLocales != localesChars.c_str()) {
+            sSingleEntryLocalesCache.javaLocales = localesChars.c_str();
+            toLanguageTags(&sSingleEntryLocalesCache.languageTags, localesChars.c_str());
+        }
+
+        obj->setTextLocales(sSingleEntryLocalesCache.languageTags);
     }
 
     static jboolean isElegantTextHeight(JNIEnv* env, jobject, jlong paintHandle) {
@@ -952,7 +991,7 @@
     {"nSetRasterizer","!(JJ)J", (void*) PaintGlue::setRasterizer},
     {"nGetTextAlign","!(J)I", (void*) PaintGlue::getTextAlign},
     {"nSetTextAlign","!(JI)V", (void*) PaintGlue::setTextAlign},
-    {"nSetTextLocale","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocale},
+    {"nSetTextLocales","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocales},
     {"nIsElegantTextHeight","!(J)Z", (void*) PaintGlue::isElegantTextHeight},
     {"nSetElegantTextHeight","!(JZ)V", (void*) PaintGlue::setElegantTextHeight},
     {"nGetTextSize","!(J)F", (void*) PaintGlue::getTextSize},
diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h
index 6df22ff..7a34bc2 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/core/jni/android/graphics/Paint.h
@@ -53,12 +53,12 @@
         return mFontFeatureSettings;
     }
 
-    void setTextLocale(const std::string &textLocale) {
-        mTextLocale = textLocale;
+    void setTextLocales(const std::string &textLocales) {
+        mTextLocales = textLocales;
     }
 
-    const std::string& getTextLocale() const {
-        return mTextLocale;
+    const std::string& getTextLocales() const {
+        return mTextLocales;
     }
 
     void setFontVariant(FontVariant variant) {
@@ -80,7 +80,7 @@
 private:
     float mLetterSpacing = 0;
     std::string mFontFeatureSettings;
-    std::string mTextLocale;
+    std::string mTextLocales;
     FontVariant mFontVariant;
     uint32_t mHyphenEdit = 0;
 };
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp
index da85018..d5a0972 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/core/jni/android/graphics/PaintImpl.cpp
@@ -23,12 +23,12 @@
 namespace android {
 
 Paint::Paint() : SkPaint(),
-        mLetterSpacing(0), mFontFeatureSettings(), mTextLocale(), mFontVariant(VARIANT_DEFAULT) {
+        mLetterSpacing(0), mFontFeatureSettings(), mTextLocales(), mFontVariant(VARIANT_DEFAULT) {
 }
 
 Paint::Paint(const Paint& paint) : SkPaint(paint),
         mLetterSpacing(paint.mLetterSpacing), mFontFeatureSettings(paint.mFontFeatureSettings),
-        mTextLocale(paint.mTextLocale), mFontVariant(paint.mFontVariant),
+        mTextLocales(paint.mTextLocales), mFontVariant(paint.mFontVariant),
         mHyphenEdit(paint.mHyphenEdit) {
 }
 
@@ -39,7 +39,7 @@
     SkPaint::operator=(other);
     mLetterSpacing = other.mLetterSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
-    mTextLocale = other.mTextLocale;
+    mTextLocales = other.mTextLocales;
     mFontVariant = other.mFontVariant;
     mHyphenEdit = other.mHyphenEdit;
     return *this;
@@ -49,7 +49,7 @@
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
             && a.mLetterSpacing == b.mLetterSpacing
             && a.mFontFeatureSettings == b.mFontFeatureSettings
-            && a.mTextLocale == b.mTextLocale
+            && a.mTextLocales == b.mTextLocales
             && a.mFontVariant == b.mFontVariant
             && a.mHyphenEdit == b.mHyphenEdit;
 }
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index e045f5f..d4735ec 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -643,12 +643,6 @@
 
 // ---------------------------------------------------------------------------
 
-extern void setGLDebugLevel(int level);
-void setTracingLevel(JNIEnv *env, jclass clazz, jint level)
-{
-    setGLDebugLevel(level);
-}
-
 static int checkFormat(SkColorType colorType, int format, int type)
 {
     switch(colorType) {
@@ -1103,7 +1097,6 @@
     { "native_getType", "(Landroid/graphics/Bitmap;)I", (void*) util_getType },
     { "native_texImage2D", "(IIILandroid/graphics/Bitmap;II)I", (void*)util_texImage2D },
     { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D },
-    { "setTracingLevel", "(I)V",                        (void*)setTracingLevel },
 };
 
 static const JNINativeMethod gEtc1Methods[] = {
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 738a62a..cb0abb6 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -1660,10 +1660,11 @@
         uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
         uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
         if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) {
+            // GainMap rectangle is relative to the active area origin.
             err = builder.addGainMapsForMetadata(lsmWidth,
                                                  lsmHeight,
-                                                 ymin,
-                                                 xmin,
+                                                 0,
+                                                 0,
                                                  height,
                                                  width,
                                                  opcodeCfaLayout,
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 097bbac..1ee7ea8 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -294,7 +294,9 @@
                             // This is the regular Dalvik heap.
                             whichHeap = HEAP_DALVIK;
                             subHeap = HEAP_DALVIK_NORMAL;
-                        } else if (strstr(name, "/dev/ashmem/dalvik-large object space") == name) {
+                        } else if (strstr(name, "/dev/ashmem/dalvik-large object space") == name ||
+                                   strstr(name, "/dev/ashmem/dalvik-free list large object space")
+                                       == name) {
                             whichHeap = HEAP_DALVIK;
                             subHeap = HEAP_DALVIK_LARGE;
                         } else if (strstr(name, "/dev/ashmem/dalvik-non moving space") == name) {
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index a14afa0..41aa9ca 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -114,7 +114,7 @@
     return parcel ? parcel->dataCapacity() : 0;
 }
 
-static void android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jlong nativePtr, jint size)
+static jlong android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jlong nativePtr, jint size)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -122,7 +122,9 @@
         if (err != NO_ERROR) {
             signalExceptionForError(env, clazz, err);
         }
+        return parcel->getOpenAshmemSize();
     }
+    return 0;
 }
 
 static void android_os_Parcel_setDataPosition(JNIEnv* env, jclass clazz, jlong nativePtr, jint pos)
@@ -304,7 +306,7 @@
     }
 }
 
-static void android_os_Parcel_writeFileDescriptor(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
+static jlong android_os_Parcel_writeFileDescriptor(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -313,7 +315,9 @@
         if (err != NO_ERROR) {
             signalExceptionForError(env, clazz, err);
         }
+        return parcel->getOpenAshmemSize();
     }
+    return 0;
 }
 
 static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -546,12 +550,14 @@
     return reinterpret_cast<jlong>(parcel);
 }
 
-static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jlong android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
         parcel->freeData();
+        return parcel->getOpenAshmemSize();
     }
+    return 0;
 }
 
 static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -589,12 +595,12 @@
     return ret;
 }
 
-static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
-                                         jbyteArray data, jint offset, jint length)
+static jlong android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
+                                          jbyteArray data, jint offset, jint length)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel == NULL || length < 0) {
-       return;
+       return 0;
     }
 
     jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
@@ -608,24 +614,26 @@
 
         env->ReleasePrimitiveArrayCritical(data, array, 0);
     }
+    return parcel->getOpenAshmemSize();
 }
 
-static void android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr,
-                                         jlong otherNativePtr, jint offset, jint length)
+static jlong android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr,
+                                          jlong otherNativePtr, jint offset, jint length)
 {
     Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr);
     if (thisParcel == NULL) {
-       return;
+       return 0;
     }
     Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr);
     if (otherParcel == NULL) {
-       return;
+       return thisParcel->getOpenAshmemSize();
     }
 
     status_t err = thisParcel->appendFrom(otherParcel, offset, length);
     if (err != NO_ERROR) {
         signalExceptionForError(env, clazz, err);
     }
+    return thisParcel->getOpenAshmemSize();
 }
 
 static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -718,7 +726,7 @@
     {"nativeDataAvail",           "(J)I", (void*)android_os_Parcel_dataAvail},
     {"nativeDataPosition",        "(J)I", (void*)android_os_Parcel_dataPosition},
     {"nativeDataCapacity",        "(J)I", (void*)android_os_Parcel_dataCapacity},
-    {"nativeSetDataSize",         "(JI)V", (void*)android_os_Parcel_setDataSize},
+    {"nativeSetDataSize",         "(JI)J", (void*)android_os_Parcel_setDataSize},
     {"nativeSetDataPosition",     "(JI)V", (void*)android_os_Parcel_setDataPosition},
     {"nativeSetDataCapacity",     "(JI)V", (void*)android_os_Parcel_setDataCapacity},
 
@@ -733,7 +741,7 @@
     {"nativeWriteDouble",         "(JD)V", (void*)android_os_Parcel_writeDouble},
     {"nativeWriteString",         "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
     {"nativeWriteStrongBinder",   "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
-    {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},
+    {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
 
     {"nativeCreateByteArray",     "(J)[B", (void*)android_os_Parcel_createByteArray},
     {"nativeReadBlob",            "(J)[B", (void*)android_os_Parcel_readBlob},
@@ -751,12 +759,12 @@
     {"clearFileDescriptor",       "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
 
     {"nativeCreate",              "()J", (void*)android_os_Parcel_create},
-    {"nativeFreeBuffer",          "(J)V", (void*)android_os_Parcel_freeBuffer},
+    {"nativeFreeBuffer",          "(J)J", (void*)android_os_Parcel_freeBuffer},
     {"nativeDestroy",             "(J)V", (void*)android_os_Parcel_destroy},
 
     {"nativeMarshall",            "(J)[B", (void*)android_os_Parcel_marshall},
-    {"nativeUnmarshall",          "(J[BII)V", (void*)android_os_Parcel_unmarshall},
-    {"nativeAppendFrom",          "(JJII)V", (void*)android_os_Parcel_appendFrom},
+    {"nativeUnmarshall",          "(J[BII)J", (void*)android_os_Parcel_unmarshall},
+    {"nativeAppendFrom",          "(JJII)J", (void*)android_os_Parcel_appendFrom},
     {"nativeHasFileDescriptors",  "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
     {"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
     {"nativeEnforceInterface",    "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 81d46c3..0245d38 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -768,94 +768,94 @@
             "(JJ[Landroid/view/MotionEvent$PointerCoords;I)V",
             (void*)android_view_MotionEvent_nativeAddBatch },
     { "nativeGetDeviceId",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetDeviceId },
     { "nativeGetSource",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetSource },
     { "nativeSetSource",
-            "(JI)I",
+            "!(JI)I",
             (void*)android_view_MotionEvent_nativeSetSource },
     { "nativeGetAction",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetAction },
     { "nativeSetAction",
-            "(JI)V",
+            "!(JI)V",
             (void*)android_view_MotionEvent_nativeSetAction },
     { "nativeGetActionButton",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetActionButton},
     { "nativeSetActionButton",
-            "(JI)V",
+            "!(JI)V",
             (void*)android_view_MotionEvent_nativeSetActionButton},
     { "nativeIsTouchEvent",
-            "(J)Z",
+            "!(J)Z",
             (void*)android_view_MotionEvent_nativeIsTouchEvent },
     { "nativeGetFlags",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetFlags },
     { "nativeSetFlags",
-            "(JI)V",
+            "!(JI)V",
             (void*)android_view_MotionEvent_nativeSetFlags },
     { "nativeGetEdgeFlags",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetEdgeFlags },
     { "nativeSetEdgeFlags",
-            "(JI)V",
+            "!(JI)V",
             (void*)android_view_MotionEvent_nativeSetEdgeFlags },
     { "nativeGetMetaState",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetMetaState },
     { "nativeGetButtonState",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetButtonState },
     { "nativeSetButtonState",
-            "(JI)V",
+            "!(JI)V",
             (void*)android_view_MotionEvent_nativeSetButtonState },
     { "nativeOffsetLocation",
-            "(JFF)V",
+            "!(JFF)V",
             (void*)android_view_MotionEvent_nativeOffsetLocation },
     { "nativeGetXOffset",
-            "(J)F",
+            "!(J)F",
             (void*)android_view_MotionEvent_nativeGetXOffset },
     { "nativeGetYOffset",
-            "(J)F",
+            "!(J)F",
             (void*)android_view_MotionEvent_nativeGetYOffset },
     { "nativeGetXPrecision",
-            "(J)F",
+            "!(J)F",
             (void*)android_view_MotionEvent_nativeGetXPrecision },
     { "nativeGetYPrecision",
-            "(J)F",
+            "!(J)F",
             (void*)android_view_MotionEvent_nativeGetYPrecision },
     { "nativeGetDownTimeNanos",
-            "(J)J",
+            "!(J)J",
             (void*)android_view_MotionEvent_nativeGetDownTimeNanos },
     { "nativeSetDownTimeNanos",
-            "(JJ)V",
+            "!(JJ)V",
             (void*)android_view_MotionEvent_nativeSetDownTimeNanos },
     { "nativeGetPointerCount",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetPointerCount },
     { "nativeGetPointerId",
-            "(JI)I",
+            "!(JI)I",
             (void*)android_view_MotionEvent_nativeGetPointerId },
     { "nativeGetToolType",
-            "(JI)I",
+            "!(JI)I",
             (void*)android_view_MotionEvent_nativeGetToolType },
     { "nativeFindPointerIndex",
-            "(JI)I",
+            "!(JI)I",
             (void*)android_view_MotionEvent_nativeFindPointerIndex },
     { "nativeGetHistorySize",
-            "(J)I",
+            "!(J)I",
             (void*)android_view_MotionEvent_nativeGetHistorySize },
     { "nativeGetEventTimeNanos",
-            "(JI)J",
+            "!(JI)J",
             (void*)android_view_MotionEvent_nativeGetEventTimeNanos },
     { "nativeGetRawAxisValue",
-            "(JIII)F",
+            "!(JIII)F",
             (void*)android_view_MotionEvent_nativeGetRawAxisValue },
     { "nativeGetAxisValue",
-            "(JIII)F",
+            "!(JIII)F",
             (void*)android_view_MotionEvent_nativeGetAxisValue },
     { "nativeGetPointerCoords",
             "(JIILandroid/view/MotionEvent$PointerCoords;)V",
@@ -864,7 +864,7 @@
             "(JILandroid/view/MotionEvent$PointerProperties;)V",
             (void*)android_view_MotionEvent_nativeGetPointerProperties },
     { "nativeScale",
-            "(JF)V",
+            "!(JF)V",
             (void*)android_view_MotionEvent_nativeScale },
     { "nativeTransform",
             "(JLandroid/graphics/Matrix;)V",
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
index 3bfd645..86f288d 100644
--- a/core/jni/android_view_PointerIcon.h
+++ b/core/jni/android_view_PointerIcon.h
@@ -31,6 +31,27 @@
     POINTER_ICON_STYLE_CUSTOM = -1,
     POINTER_ICON_STYLE_NULL = 0,
     POINTER_ICON_STYLE_ARROW = 1000,
+    POINTER_ICON_STYLE_CONTEXT_MENU = 1001,
+    POINTER_ICON_STYLE_HAND = 1002,
+    POINTER_ICON_STYLE_HELP = 1003,
+    POINTER_ICON_STYLE_WAIT = 1004,
+    POINTER_ICON_STYLE_CELL = 1006,
+    POINTER_ICON_STYLE_CROSSHAIR = 1007,
+    POINTER_ICON_STYLE_TEXT = 1008,
+    POINTER_ICON_STYLE_VERTICAL_TEXT = 1009,
+    POINTER_ICON_STYLE_ALIAS = 1010,
+    POINTER_ICON_STYLE_COPY = 1011,
+    POINTER_ICON_STYLE_NO_DROP = 1012,
+    POINTER_ICON_STYLE_ALL_SCROLL = 1013,
+    POINTER_ICON_STYLE_HORIZONTAL_DOUBLE_ARROW = 1014,
+    POINTER_ICON_STYLE_VERTICAL_DOUBLE_ARROW = 1015,
+    POINTER_ICON_STYLE_TOP_RIGHT_DOUBLE_ARROW = 1016,
+    POINTER_ICON_STYLE_TOP_LEFT_DOUBLE_ARROW = 1017,
+    POINTER_ICON_STYLE_ZOOM_IN = 1018,
+    POINTER_ICON_STYLE_ZOOM_OUT = 1019,
+    POINTER_ICON_STYLE_GRAB = 1020,
+    POINTER_ICON_STYLE_GRABBING = 1021,
+
     POINTER_ICON_STYLE_SPOT_HOVER = 2000,
     POINTER_ICON_STYLE_SPOT_TOUCH = 2001,
     POINTER_ICON_STYLE_SPOT_ANCHOR = 2002,
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 388d365..b1d4e26 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -71,10 +71,10 @@
     renderNode->decStrong(0);
 }
 
-static void android_view_RenderNode_setDisplayListData(JNIEnv* env,
-        jobject clazz, jlong renderNodePtr, jlong newDataPtr) {
+static void android_view_RenderNode_setDisplayList(JNIEnv* env,
+        jobject clazz, jlong renderNodePtr, jlong displayListPtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    DisplayListData* newData = reinterpret_cast<DisplayListData*>(newDataPtr);
+    DisplayList* newData = reinterpret_cast<DisplayList*>(displayListPtr);
     renderNode->setStagingDisplayList(newData);
 }
 
@@ -468,11 +468,11 @@
 const char* const kClassPathName = "android/view/RenderNode";
 
 static const JNINativeMethod gMethods[] = {
-    { "nCreate",               "(Ljava/lang/String;)J",    (void*) android_view_RenderNode_create },
-    { "nDestroyRenderNode",   "(J)V",   (void*) android_view_RenderNode_destroyRenderNode },
-    { "nSetDisplayListData",   "(JJ)V", (void*) android_view_RenderNode_setDisplayListData },
-    { "nOutput",               "(J)V",  (void*) android_view_RenderNode_output },
-    { "nGetDebugSize",         "(J)I",  (void*) android_view_RenderNode_getDebugSize },
+    { "nCreate",               "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create },
+    { "nDestroyRenderNode",    "(J)V",    (void*) android_view_RenderNode_destroyRenderNode },
+    { "nSetDisplayList",       "(JJ)V",   (void*) android_view_RenderNode_setDisplayList },
+    { "nOutput",               "(J)V",    (void*) android_view_RenderNode_output },
+    { "nGetDebugSize",         "(J)I",    (void*) android_view_RenderNode_getDebugSize },
 
     { "nSetLayerType",         "!(JI)Z",  (void*) android_view_RenderNode_setLayerType },
     { "nSetLayerPaint",        "!(JJ)Z",  (void*) android_view_RenderNode_setLayerPaint },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5e9baa2..39eda58 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1714,12 +1714,12 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- Allows applications to change network connectivity state.
-         <p>Protection level: signature
+         <p>Protection level: normal
     -->
     <permission android:name="android.permission.CHANGE_NETWORK_STATE"
         android:description="@string/permdesc_changeNetworkState"
         android:label="@string/permlab_changeNetworkState"
-        android:protectionLevel="signature|preinstalled|appop|pre23" />
+        android:protectionLevel="normal" />
 
     <!-- Allows an application to clear the caches of all installed
          applications on the device.
@@ -2074,10 +2074,11 @@
     <permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
         android:protectionLevel="signature" />
 
-    <!-- Allows an application to monitor changes in tablet mode.
+    <!-- Allows an application to query tablet mode state and monitor changes
+         in it.
          <p>Not for use by third-party applications.
          @hide -->
-    <permission android:name="android.permission.TABLET_MODE_LISTENER"
+    <permission android:name="android.permission.TABLET_MODE"
         android:protectionLevel="signature" />
 
     <!-- Allows an application to request installing packages. Apps
diff --git a/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml b/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
index baa8958..905fd4d 100644
--- a/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
+++ b/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
@@ -1,8 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
+        android:state_enabled="false"
+        android:color="?attr/textColorSecondaryInverse"
+        android:alpha="?attr/disabledAlpha" />
+    <item
         android:state_activated="true"
         android:color="?attr/textColorPrimaryInverse" />
     <item
         android:color="?attr/textColorSecondaryInverse" />
-</selector>
\ No newline at end of file
+</selector>
diff --git a/core/res/res/drawable-mdpi/pointer_alias.png b/core/res/res/drawable-mdpi/pointer_alias.png
new file mode 100644
index 0000000..8f61a39
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png
new file mode 100644
index 0000000..a897ef4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_cell.png b/core/res/res/drawable-mdpi/pointer_cell.png
new file mode 100644
index 0000000..b521389
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png
new file mode 100644
index 0000000..4e1ba4e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_copy.png b/core/res/res/drawable-mdpi/pointer_copy.png
new file mode 100644
index 0000000..7d41036
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_crosshair.png b/core/res/res/drawable-mdpi/pointer_crosshair.png
new file mode 100644
index 0000000..8a06c77
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grab.png b/core/res/res/drawable-mdpi/pointer_grab.png
new file mode 100644
index 0000000..0e0ea2e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grabbing.png b/core/res/res/drawable-mdpi/pointer_grabbing.png
new file mode 100644
index 0000000..9deb64c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_hand.png b/core/res/res/drawable-mdpi/pointer_hand.png
new file mode 100644
index 0000000..c614d9e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png
new file mode 100644
index 0000000..d54b2b1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
new file mode 100644
index 0000000..a2951a9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_nodrop.png b/core/res/res/drawable-mdpi/pointer_nodrop.png
new file mode 100644
index 0000000..ad13c66
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_text.png b/core/res/res/drawable-mdpi/pointer_text.png
new file mode 100644
index 0000000..34d1698
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
new file mode 100644
index 0000000..b0cd92c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
new file mode 100644
index 0000000..f8d3527
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
new file mode 100644
index 0000000..48c9379
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text.png b/core/res/res/drawable-mdpi/pointer_vertical_text.png
new file mode 100644
index 0000000..9fcbcba
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in.png b/core/res/res/drawable-mdpi/pointer_zoom_in.png
new file mode 100644
index 0000000..17c4e66
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out.png b/core/res/res/drawable-mdpi/pointer_zoom_out.png
new file mode 100644
index 0000000..742f705
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_alias.png b/core/res/res/drawable-xhdpi/pointer_alias.png
new file mode 100644
index 0000000..fe0fd25
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
new file mode 100644
index 0000000..e2374ec
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_cell.png b/core/res/res/drawable-xhdpi/pointer_cell.png
new file mode 100644
index 0000000..4ca09e3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png
new file mode 100644
index 0000000..05a59f8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_copy.png b/core/res/res/drawable-xhdpi/pointer_copy.png
new file mode 100644
index 0000000..f28a3e9
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair.png b/core/res/res/drawable-xhdpi/pointer_crosshair.png
new file mode 100644
index 0000000..86c649c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grab.png b/core/res/res/drawable-xhdpi/pointer_grab.png
new file mode 100644
index 0000000..b5c28ba
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing.png b/core/res/res/drawable-xhdpi/pointer_grabbing.png
new file mode 100644
index 0000000..6aba589
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_hand.png b/core/res/res/drawable-xhdpi/pointer_hand.png
new file mode 100644
index 0000000..486cb24
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png
new file mode 100644
index 0000000..98a6632
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
new file mode 100644
index 0000000..299ae11
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop.png b/core/res/res/drawable-xhdpi/pointer_nodrop.png
new file mode 100644
index 0000000..c56bfbb
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_text.png b/core/res/res/drawable-xhdpi/pointer_text.png
new file mode 100644
index 0000000..0cebeae
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
new file mode 100644
index 0000000..5454a8b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
new file mode 100644
index 0000000..a4268e4
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
new file mode 100644
index 0000000..95ca954
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text.png b/core/res/res/drawable-xhdpi/pointer_vertical_text.png
new file mode 100644
index 0000000..a07d091
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in.png b/core/res/res/drawable-xhdpi/pointer_zoom_in.png
new file mode 100644
index 0000000..9c29fcb
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out.png b/core/res/res/drawable-xhdpi/pointer_zoom_out.png
new file mode 100644
index 0000000..710552b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_alias_icon.xml b/core/res/res/drawable/pointer_alias_icon.xml
new file mode 100644
index 0000000..8ba9301
--- /dev/null
+++ b/core/res/res/drawable/pointer_alias_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_alias"
+    android:hotSpotX="8dp"
+    android:hotSpotY="6dp" />
diff --git a/core/res/res/drawable/pointer_all_scroll_icon.xml b/core/res/res/drawable/pointer_all_scroll_icon.xml
new file mode 100644
index 0000000..e946948
--- /dev/null
+++ b/core/res/res/drawable/pointer_all_scroll_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_all_scroll"
+    android:hotSpotX="11dp"
+    android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_cell_icon.xml b/core/res/res/drawable/pointer_cell_icon.xml
new file mode 100644
index 0000000..da1e320
--- /dev/null
+++ b/core/res/res/drawable/pointer_cell_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_cell"
+    android:hotSpotX="11dp"
+    android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_context_menu_icon.xml b/core/res/res/drawable/pointer_context_menu_icon.xml
new file mode 100644
index 0000000..330b627
--- /dev/null
+++ b/core/res/res/drawable/pointer_context_menu_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_context_menu"
+    android:hotSpotX="4dp"
+    android:hotSpotY="4dp" />
diff --git a/core/res/res/drawable/pointer_copy_icon.xml b/core/res/res/drawable/pointer_copy_icon.xml
new file mode 100644
index 0000000..e299db5
--- /dev/null
+++ b/core/res/res/drawable/pointer_copy_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_copy"
+    android:hotSpotX="9dp"
+    android:hotSpotY="9dp" />
diff --git a/core/res/res/drawable/pointer_crosshair_icon.xml b/core/res/res/drawable/pointer_crosshair_icon.xml
new file mode 100644
index 0000000..3b96a8a
--- /dev/null
+++ b/core/res/res/drawable/pointer_crosshair_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_crosshair"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_grab_icon.xml b/core/res/res/drawable/pointer_grab_icon.xml
new file mode 100644
index 0000000..d437b3a
--- /dev/null
+++ b/core/res/res/drawable/pointer_grab_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_grab"
+    android:hotSpotX="8dp"
+    android:hotSpotY="5dp" />
diff --git a/core/res/res/drawable/pointer_grabbing_icon.xml b/core/res/res/drawable/pointer_grabbing_icon.xml
new file mode 100644
index 0000000..38f4c3a
--- /dev/null
+++ b/core/res/res/drawable/pointer_grabbing_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_grabbing"
+    android:hotSpotX="9dp"
+    android:hotSpotY="9dp" />
diff --git a/core/res/res/drawable/pointer_hand_icon.xml b/core/res/res/drawable/pointer_hand_icon.xml
new file mode 100644
index 0000000..3d90b88
--- /dev/null
+++ b/core/res/res/drawable/pointer_hand_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_hand"
+    android:hotSpotX="9dp"
+    android:hotSpotY="4dp" />
diff --git a/core/res/res/drawable/pointer_help_icon.xml b/core/res/res/drawable/pointer_help_icon.xml
new file mode 100644
index 0000000..49ae554
--- /dev/null
+++ b/core/res/res/drawable/pointer_help_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_help"
+    android:hotSpotX="4dp"
+    android:hotSpotY="4dp" />
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
new file mode 100644
index 0000000..5a5ad9e
--- /dev/null
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_horizontal_double_arrow"
+    android:hotSpotX="11dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_icon.xml b/core/res/res/drawable/pointer_nodrop_icon.xml
new file mode 100644
index 0000000..955b40f
--- /dev/null
+++ b/core/res/res/drawable/pointer_nodrop_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_nodrop"
+    android:hotSpotX="9dp"
+    android:hotSpotY="9dp" />
diff --git a/core/res/res/drawable/pointer_text_icon.xml b/core/res/res/drawable/pointer_text_icon.xml
new file mode 100644
index 0000000..d948c89
--- /dev/null
+++ b/core/res/res/drawable/pointer_text_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_text"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
new file mode 100644
index 0000000..de5efe2
--- /dev/null
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow"
+    android:hotSpotX="11dp"
+    android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
new file mode 100644
index 0000000..e87b526
--- /dev/null
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow"
+    android:hotSpotX="12dp"
+    android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
new file mode 100644
index 0000000..5759079
--- /dev/null
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_vertical_double_arrow"
+    android:hotSpotX="11dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_vertical_text_icon.xml b/core/res/res/drawable/pointer_vertical_text_icon.xml
new file mode 100644
index 0000000..3b48dc8
--- /dev/null
+++ b/core/res/res/drawable/pointer_vertical_text_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_vertical_text"
+    android:hotSpotX="12dp"
+    android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_zoom_in_icon.xml b/core/res/res/drawable/pointer_zoom_in_icon.xml
new file mode 100644
index 0000000..e2dcb49
--- /dev/null
+++ b/core/res/res/drawable/pointer_zoom_in_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_zoom_in"
+    android:hotSpotX="10dp"
+    android:hotSpotY="10dp" />
diff --git a/core/res/res/drawable/pointer_zoom_out_icon.xml b/core/res/res/drawable/pointer_zoom_out_icon.xml
new file mode 100644
index 0000000..b805df3
--- /dev/null
+++ b/core/res/res/drawable/pointer_zoom_out_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_zoom_out"
+    android:hotSpotX="10dp"
+    android:hotSpotY="10dp" />
diff --git a/core/res/res/drawable/spinner_background_material.xml b/core/res/res/drawable/spinner_background_material.xml
index d37f5b7..c2a2a26 100644
--- a/core/res/res/drawable/spinner_background_material.xml
+++ b/core/res/res/drawable/spinner_background_material.xml
@@ -17,18 +17,18 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
             android:paddingMode="stack"
             android:paddingStart="0dp"
-            android:paddingEnd="24dp"
+            android:paddingEnd="48dp"
             android:paddingLeft="0dp"
             android:paddingRight="0dp">
     <item
-        android:gravity="end|center_vertical"
-        android:width="24dp"
-        android:height="24dp"
+        android:gravity="end|fill_vertical"
+        android:width="48dp"
         android:drawable="@drawable/control_background_40dp_material" />
 
     <item
         android:drawable="@drawable/ic_spinner_caret"
         android:gravity="end|center_vertical"
         android:width="24dp"
-        android:height="24dp" />
+        android:height="24dp"
+        android:end="12dp" />
 </layer-list>
diff --git a/core/res/res/drawable/time_picker_editable_background.xml b/core/res/res/drawable/time_picker_editable_background.xml
new file mode 100644
index 0000000..72e863b
--- /dev/null
+++ b/core/res/res/drawable/time_picker_editable_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <layer-list>
+            <item android:gravity="bottom"
+                  android:height="10dp">
+                <shape android:shape="line"
+                       android:tint="?attr/textColorPrimaryInverse">
+                    <stroke android:color="@color/white"
+                            android:width="2dp" />
+                </shape>
+            </item>
+        </layer-list>
+    </item>
+    <item android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index bb347cb..7a0c38f 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -51,15 +51,17 @@
 
             <!-- The hour should always be to the left of the separator,
                  regardless of the current locale's layout direction. -->
-            <TextView
+            <com.android.internal.widget.NumericTextView
                 android:id="@+id/hours"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+                android:background="@drawable/time_picker_editable_background"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:gravity="right"
-                android:includeFontPadding="false" />
+                android:focusable="true"
+                android:nextFocusForward="@+id/minutes" />
 
             <TextView
                 android:id="@+id/separator"
@@ -71,57 +73,56 @@
 
             <!-- The minutes should always be to the right of the separator,
                  regardless of the current locale's layout direction. -->
-            <TextView
+            <com.android.internal.widget.NumericTextView
                 android:id="@+id/minutes"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+                android:background="@drawable/time_picker_editable_background"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:gravity="left"
-                android:includeFontPadding="false" />
+                android:focusable="true"
+                android:nextFocusForward="@+id/am_label" />
         </LinearLayout>
 
-        <!-- The layout alignment of this view will switch between toRightOf
-             @id/minutes and toLeftOf @id/hours depending on the locale. -->
-        <LinearLayout
+        <RadioGroup
             android:id="@+id/ampm_layout"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_below="@+id/time_layout"
             android:layout_centerHorizontal="true"
+            android:paddingTop="4dp"
+            android:paddingBottom="4dp"
             android:orientation="vertical"
             android:layoutDirection="locale">
-
-            <CheckedTextView
+            <RadioButton
                 android:id="@+id/am_label"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minHeight="48dp"
-                android:minWidth="48dp"
-                android:gravity="bottom"
+                android:padding="8dp"
+                android:layout_marginBottom="-8dp"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
-                android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
-                android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
-                android:paddingTop="4dp"
-                android:paddingBottom="6dp"
                 android:lines="1"
-                android:ellipsize="none" />
-
-            <CheckedTextView
+                android:ellipsize="none"
+                android:focusable="true"
+                android:background="?android:attr/selectableItemBackground"
+                android:includeFontPadding="false"
+                android:nextFocusForward="@+id/pm_label"
+                android:button="@null" />
+            <RadioButton
                 android:id="@+id/pm_label"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minHeight="48dp"
-                android:minWidth="48dp"
-                android:gravity="top"
+                android:padding="8dp"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
-                android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
-                android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
                 android:lines="1"
                 android:ellipsize="none"
-                android:includeFontPadding="false" />
-        </LinearLayout>
+                android:focusable="true"
+                android:background="?android:attr/selectableItemBackground"
+                android:includeFontPadding="false"
+                android:button="@null" />
+        </RadioGroup>
     </RelativeLayout>
 
     <ViewStub
diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml
index acdc509..7019ced 100644
--- a/core/res/res/layout/time_picker_header_material.xml
+++ b/core/res/res/layout/time_picker_header_material.xml
@@ -21,23 +21,24 @@
                 android:id="@+id/time_header"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:orientation="horizontal"
-                android:padding="@dimen/timepicker_separator_padding"
                 android:paddingStart="16dp"
                 android:paddingEnd="16dp">
 
     <!-- The hour should always be to the left of the separator,
          regardless of the current locale's layout direction. -->
-    <TextView
+    <com.android.internal.widget.NumericTextView
         android:id="@+id/hours"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toLeftOf="@+id/separator"
         android:layout_alignBaseline="@+id/separator"
         android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+        android:background="@drawable/time_picker_editable_background"
         android:singleLine="true"
         android:ellipsize="none"
-        android:gravity="right" />
+        android:gravity="right"
+        android:focusable="true"
+        android:nextFocusForward="@+id/minutes" />
 
     <TextView
         android:id="@+id/separator"
@@ -51,50 +52,57 @@
 
     <!-- The minutes should always be to the left of the separator,
          regardless of the current locale's layout direction. -->
-    <TextView
+    <com.android.internal.widget.NumericTextView
         android:id="@+id/minutes"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toRightOf="@+id/separator"
         android:layout_alignBaseline="@+id/separator"
         android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+        android:background="@drawable/time_picker_editable_background"
         android:singleLine="true"
         android:ellipsize="none"
-        android:gravity="left" />
+        android:gravity="left"
+        android:focusable="true"
+        android:nextFocusForward="@+id/am_label" />
 
     <!-- The layout alignment of this view will switch between toRightOf
          @id/minutes and toLeftOf @id/hours depending on the locale. -->
-    <LinearLayout
+    <RadioGroup
         android:id="@+id/ampm_layout"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toRightOf="@+id/minutes"
         android:layout_alignBaseline="@+id/minutes"
-        android:paddingStart="8dp"
-        android:paddingEnd="8dp"
+        android:paddingStart="4dp"
+        android:paddingEnd="4dp"
         android:orientation="vertical"
         android:baselineAlignedChildIndex="1">
-        <CheckedTextView
+        <RadioButton
             android:id="@+id/am_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="48dp"
-            android:minHeight="48dp"
-            android:gravity="bottom"
-            android:paddingTop="@dimen/timepicker_am_top_padding"
+            android:padding="8dp"
+            android:layout_marginBottom="-8dp"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
             android:lines="1"
-            android:ellipsize="none" />
-        <CheckedTextView
+            android:ellipsize="none"
+            android:focusable="true"
+            android:background="?android:attr/selectableItemBackground"
+            android:includeFontPadding="false"
+            android:nextFocusForward="@+id/pm_label"
+            android:button="@null" />
+        <RadioButton
             android:id="@+id/pm_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="48dp"
-            android:minHeight="48dp"
-            android:gravity="top"
-            android:paddingTop="@dimen/timepicker_pm_top_padding"
+            android:padding="8dp"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
             android:lines="1"
-            android:ellipsize="none" />
-    </LinearLayout>
+            android:ellipsize="none"
+            android:focusable="true"
+            android:background="?android:attr/selectableItemBackground"
+            android:includeFontPadding="false"
+            android:button="@null" />
+    </RadioGroup>
 </RelativeLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 2f1627b..e7acf7e 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Verkeerde PIN-kode."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Om te ontsluit, druk Kieslys dan 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Noodnommer"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Geen diens nie."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Geen diens nie"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skerm gesluit."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Druk kieslys om oop te sluit of maak noodoproep."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Druk kieslys om oop te maak."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 uur lank</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgende wekker)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Totdat jy dit afskakel"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Totdat jy Moenie steur nie afskakel"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 5d94a69..5bd9ccf 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"ትክክል ያልሆነ ፒን  ኮድ።"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"ለመክፈት፣ምናሌ ተጫን ከዛ 0"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"የአደጋ ጊዜቁጥር"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"ከአገልግሎት መስጫ ክልል ውጪ"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"ምንም አገልግሎት የለም"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ማሳያ መቆለፊያ።"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"ለመክፈት ምናሌ ተጫንወይም የአደጋ ጊዜ ጥሪ አድርግ።"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"ለመክፈት ምናሌ ተጫን"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">ለ%d ሰዓት</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"እስከ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ድረስ"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"እስከ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ቀጣይ ማንቂያ)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ይህን እስኪያጠፉት ድረስ"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"አትረብሽን እስኪያጠፉ ድረስ"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 209e1dc..4dd5144 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -654,7 +654,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"‏رمز PIN غير صحيح."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"لإلغاء التأمين، اضغط على \"القائمة\" ثم على 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"رقم الطوارئ"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"لا تتوفر خدمة"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"لا خدمة"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"الشاشة مؤمّنة."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"اضغط على \"القائمة\" لإلغاء التأمين أو إجراء اتصال بالطوارئ."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"اضغط على \"القائمة\" لإلغاء التأمين."</string>
@@ -1538,6 +1538,7 @@
       <item quantity="one">لمدة ساعة</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"حتى <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"حتى <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (التنبيه التالي)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"لحين تعطيل هذا الإعداد"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"حتى يتم تعطيل \"الرجاء عدم الإزعاج\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml
index 16d123b..c828fc4 100644
--- a/core/res/res/values-az-rAZ/strings.xml
+++ b/core/res/res/values-az-rAZ/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Yanlış PIN kodu."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Kilidi açmaq üçün Menyu, sonra 0 basın."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Təcili nömrə"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Xidmət yoxdur."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Xidmət yoxdur"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekran kilidlənib."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Təcili zəng kilidini açmaq və ya yerləşdirmək üçün Menyu düyməsinə basın."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Kilidi açmaq üçün Menyu düyməsinə basın."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 saat üçün</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Saat <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> qədər"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> radəsinə qədər (növbəti siqnal)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Bunu söndürənə kimi"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Narahat etməyin\" seçiminini deaktiv edənə kimi"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 5427659..e474e7e 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Неправилен ПИН код."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"За да отключите, натиснете „Меню“ и после 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Спешен номер"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Няма покритие."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Няма покритие"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Екранът е заключен."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Натиснете „Меню“, за да отключите или да извършите спешно обаждане."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Натиснете „Меню“, за да отключите."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">За 1 ч</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"До следващия будилник (<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Докато не изключите това"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Докато не изключите „Не безпокойте“"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml
index 2d847ee..1f365b9 100644
--- a/core/res/res/values-bn-rBD/strings.xml
+++ b/core/res/res/values-bn-rBD/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"ভুল পিন কোড৷"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"আনলক করতে, মেনু টিপুন তারপর ০ টিপুন৷"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"জরুরী নম্বর"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"কোনো পরিষেবা নেই৷"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"কোনো পরিষেবা নেই"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"স্ক্রীণ লক করা আছে৷"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"আনলক করতে বা জরুরী কল করতে মেনু টিপুন৷"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"আনলক করতে মেনু টিপুন৷"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">%d ঘন্টার জন্য</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত (পরবর্তী অ্যালার্ম)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"আপনার দ্বারা এটি বন্ধ করা পর্যন্ত"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"যতক্ষণ না পর্যন্ত আপনি বিরক্ত করবেন না বন্ধ করছেন"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index e32c841..046ca14 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Codi PIN incorrecte."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Per desbloquejar-lo, premeu Menú i després 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número d\'emergència"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Sense servei."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sense servei"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Pantalla bloquejada."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Premeu Menú per desbloquejar-lo o per fer una trucada d\'emergència."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Premeu Menú per desbloquejar."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Durant 1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (propera alarma)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Fins que no ho desactivis"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Fins que desactivis el mode No molesteu"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 7b05612..8f3976d 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Nesprávný kód PIN."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Chcete-li telefon odemknout, stiskněte Menu a poté 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Číslo tísňové linky"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Žádný signál"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Žádný signál"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Obrazovka uzamčena."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Chcete-li odemknout telefon nebo provést tísňové volání, stiskněte Menu."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Telefon odemknete stisknutím tlačítka Menu."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="one">1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (příští budík)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dokud tuto funkci nevypnete"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dokud nevypnete režim Nerušit"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 9c420c2..82b550b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -219,7 +219,7 @@
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flytilstand er slået FRA"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Indstillinger"</string>
     <string name="global_action_assist" msgid="3892832961594295030">"Assistance"</string>
-    <string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
+    <string name="global_action_voice_assist" msgid="7751191495200504480">"Taleassistent"</string>
     <string name="global_action_lockdown" msgid="8751542514724332873">"Lås nu"</string>
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Sikker tilstand"</string>
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Forkert pinkode."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Tryk på Menu og dernæst på 0 for at låse op."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ingen dækning."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ingen dækning"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skærmen er låst."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Tryk på Menu for at låse op eller foretage et nødopkald."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tryk på Menu for at låse op."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">I %d t.</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Indtil <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Indtil <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (næste alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Indtil du slår denne indstilling fra"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Indtil du slår \"Forstyr ikke\" fra"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index fcbd762..9ae5473b 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Falscher PIN-Code"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Drücken Sie zum Entsperren die Menütaste und dann auf \"0\"."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Notrufnummer"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Kein Dienst"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Kein Dienst"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Display gesperrt"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Drücken Sie die Menütaste, um das Telefon zu entsperren oder einen Notruf zu tätigen."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Zum Entsperren die Menütaste drücken"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Für 1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nächste Weckzeit)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Bis zur Deaktivierung"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Bis zur Deaktivierung von \"Nicht stören\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index f2bb1d5..56deed13 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Λανθασμένος κωδικός PIN."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Για ξεκλείδωμα, πατήστε το πλήκτρο Menu και, στη συνέχεια, το πλήκτρο 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Αριθμός έκτακτης ανάγκης"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Καμία υπηρεσία."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Δίκτυο μη διαθέσιμο"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Η οθόνη κλειδώθηκε."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Πατήστε \"Menu\" για ξεκλείδωμα ή για κλήση έκτακτης ανάγκης."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Πατήστε \"Μενού\" για ξεκλείδωμα."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Για 1 ώρα</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Έως τις <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Μέχρι τις <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (επόμενο ξυπνητήρι)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Μέχρι να το απενεργοποιήσετε"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Μέχρι να απενεργοποιήσετε τη ρύθμιση \"Μην ενοχλείτε\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index ea33448..53adc2b 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Incorrect PIN code."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"To unlock, press Menu, then 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Emergency number"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"No service"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"No service"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Screen locked."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Press Menu to unlock or place emergency call."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Press Menu to unlock."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">For 1 hr</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Until you turn off Do Not Disturb"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index ea33448..53adc2b 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Incorrect PIN code."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"To unlock, press Menu, then 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Emergency number"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"No service"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"No service"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Screen locked."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Press Menu to unlock or place emergency call."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Press Menu to unlock."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">For 1 hr</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Until you turn off Do Not Disturb"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index ea33448..53adc2b 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Incorrect PIN code."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"To unlock, press Menu, then 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Emergency number"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"No service"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"No service"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Screen locked."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Press Menu to unlock or place emergency call."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Press Menu to unlock."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">For 1 hr</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Until you turn off Do Not Disturb"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 5782eab..25ba6db8 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Código PIN incorrecto"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, presiona el menú y luego 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Sin servicio"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sin servicio"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Pantalla bloqueada."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Presiona el Menú para desbloquear o realizar una llamada de emergencia."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Presionar Menú para desbloquear."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Durante 1 hora</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Hasta la(s) <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Hasta la hora <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hasta que lo desactives"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hasta que desactives No molestar"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index bb44ed3..e3a4b66 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Código PIN incorrecto"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Sin servicio"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sin servicio"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Pantalla bloqueada"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Pulsa la tecla de menú para desbloquear el teléfono o realizar una llamada de emergencia."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Pulsa la tecla de menú para desbloquear la pantalla."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Durante 1 hora</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Hasta las <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Hasta las <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hasta apagar el dispositivo"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hasta que desactives la opción No molestar"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 98818ad..41474a4 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Vale PIN-kood."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Avamiseks vajutage menüüklahvi, seejärel klahvi 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Hädaabinumber"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Teenus puudub."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Teenus puudub"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekraan lukus."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Vajutage avamiseks või hädaabikõne tegemiseks menüünuppu"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Vajutage avamiseks menüüklahvi."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Kuni <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Kuni <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (järgmine äratus)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Kuni lülitate selle välja"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Kuni lülitate välja valiku Mitte segada"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml
index 8e7193a..3a23fba 100644
--- a/core/res/res/values-eu-rES/strings.xml
+++ b/core/res/res/values-eu-rES/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN kode okerra."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Desblokeatzeko, sakatu Menua eta, ondoren, 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Larrialdietarako zenbakia"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ez dago zerbitzurik."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ez dago zerbitzurik"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Pantaila blokeatuta dago."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Desblokeatzeko edo larrialdi-deia egiteko, sakatu Menua."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Desblokeatzeko, sakatu Menua."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Ordubetez</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> arte"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> arte (hurrengo alarma)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Zuk desaktibatu arte"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Ez molestatu\" desaktibatzen duzun arte"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 5032c25..ae6598b 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -397,16 +397,16 @@
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"‏به برنامه اجازه می‌دهد تا بسته‌هایی را دریافت کند که در شبکه Wi-Fi با استفاده از آدرس‌های چندبخشی نه تنها به تلویزیون شما، بلکه به همه دستگاهها ارسال می‌شود. این حالت نسبت به حالت غیر چندبخشی از انرژی بیشتری استفاده می‌کند."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"‏به برنامه اجازه می‌دهد به دریافت بسته‌های ارسالی به همه دستگاه‌های موجود در شبکه Wi-Fi با استفاده از آدرس‌های پخش چندگانه و نه فقط به تلفن شما بپردازند. این از توان مصرف بیشتری نسبت به حالت پخش غیرچندگانه استفاده می‌کند."</string>
     <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"دسترسی به تنظیمات بلوتوث"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"‏به برنامه اجازه می‎دهد تا رایانهٔ لوحی بلوتوث محلی را پیکربندی کرده، دستگاه‌های راه دور را شناسایی کرده و با آن‌ها جفت شود."</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"‏به برنامه اجازه می‎دهد تا رایانهٔ لوحی بلوتوث محلی را پیکربندی کرده، دستگاه‌های راه دور را شناسایی کرده و با آن‌ها مرتبط‌سازی شود."</string>
     <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"به برنامه اجازه می‌دهد تا تلویزیون بلوتوث محلی را پیکربندی کند و دستگاه‌های از راه دور را شناسایی کند و با آنها مرتبط شود."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"‏به برنامه اجازه می‎دهد تا تلفن بلوتوث محلی را پیکربندی کند و دستگاه‌های راه دور را پیدا کند و با آن‌ها جفت شود."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"‏به برنامه اجازه می‎دهد تا تلفن بلوتوث محلی را پیکربندی کند و دستگاه‌های راه دور را پیدا کند و با آن‌ها مرتبط‌سازی شود."</string>
     <string name="permlab_accessWimaxState" msgid="4195907010610205703">"‏اتصال و قطع اتصال از WiMAX"</string>
     <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"به برنامه امکان می‌دهد فعال بودن وایمکس و اطلاعات مربوط به هر یک از شبکه‌های وایمکس متصل را مشخص کند."</string>
     <string name="permlab_changeWimaxState" msgid="2405042267131496579">"‏تغییر وضعیت WiMAX"</string>
     <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"به برنامه امکان می‌دهد رایانهٔ لوحی را به شبکه‌های وایمکس متصل کرده یا اتصال آن را از این شبکه‌ها قطع کند."</string>
     <string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"‏به برنامه اجازه می‌دهد تا تلویزیون را به شبکه‌های WiMAX وصل یا ارتباط آن را با این شبکه‌ها قطع کند."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"‏به برنامه امکان می‎دهد تا تلفن را به شبکه‌های وایمکس متصل کرده یا اتصال آنرا از این شبکه‌ها قطع کند."</string>
-    <string name="permlab_bluetooth" msgid="6127769336339276828">"جفت کردن با دستگاه‌های بلوتوث"</string>
+    <string name="permlab_bluetooth" msgid="6127769336339276828">"مرتبط‌سازی با دستگاه‌های بلوتوث"</string>
     <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"‏به برنامه اجازه می‎دهد تا پیکربندی بلوتوث در رایانهٔ لوحی را مشاهده کند و اتصال با دستگاه‌های مرتبط را برقرار کرده و بپذیرد."</string>
     <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"به برنامه اجازه می‌دهد تا پیکربندی بلوتوث را در تلویزیون مشاهده کند و اتصالات را با دستگاه‌های مرتبط‌شده ایجاد کند و بپذیرد."</string>
     <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"‏به برنامه اجازه می‎دهد تا پیکربندی بلوتوث در تلفن را مشاهده کند، و اتصالات دستگاه‌های مرتبط را برقرار کرده و بپذیرد."</string>
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"پین کد اشتباه است."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"برای بازگشایی قفل، منو را فشار دهید و سپس 0 را فشار دهید."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"شماره اضطراری"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"سرویسی وجود ندارد."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"بدون سرویس"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"صفحه قفل شد."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"برای بازگشایی قفل یا انجام تماس اضطراری روی منو فشار دهید."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"برای بازگشایی قفل روی منو فشار دهید."</string>
@@ -1143,7 +1143,7 @@
     <string name="gpsNotifTitle" msgid="5446858717157416839">"درخواست موقعیت مکانی"</string>
     <string name="gpsNotifMessage" msgid="1374718023224000702">"درخواست شده توسط <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SERVICE">%2$s</xliff:g>)"</string>
     <string name="gpsVerifYes" msgid="2346566072867213563">"بله"</string>
-    <string name="gpsVerifNo" msgid="1146564937346454865">"خیر"</string>
+    <string name="gpsVerifNo" msgid="1146564937346454865">"نه"</string>
     <string name="sync_too_many_deletes" msgid="5296321850662746890">"از حد مجاز حذف فراتر رفت"</string>
     <string name="sync_too_many_deletes_desc" msgid="496551671008694245">"‏<xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> مورد حذف شده برای <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>، حساب <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g> وجود دارد. می‎خواهید چه کاری انجام دهید؟"</string>
     <string name="sync_really_delete" msgid="2572600103122596243">"حذف موارد"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">‏برای %d ساعت</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"تا <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"تا <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (هشدار بعدی)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"تا وقتی آن را خاموش کنید"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"تا زمانی که «مزاحم نشوید» را خاموش کنید"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> /‏ <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 7298cba..d8778a8 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN-koodi väärin."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Poista lukitus painamalla Valikko-painiketta ja 0-näppäintä."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Hätänumero"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ei yhteyttä."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ei yhteyttä"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Näyttö lukittu."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Poista lukitus tai soita hätäpuhelu painamalla Valikko-painiketta."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Poista lukitus painamalla Valikko-painiketta."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 tunnin ajan</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Kunnes kello on <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> asti (seuraava hälytys)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Kunnes poistat tämän käytöstä"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Kunnes poistat Varattu-tilan käytöstä."</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index ba17051..6f59ee0 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"NIP erroné."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Pour déverrouiller le téléphone, appuyez sur \"Menu\", puis sur 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numéro d\'urgence"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Aucun service"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Aucun service"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Écran verrouillé"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Appuyez sur \"Menu\" pour débloquer le téléphone ou appeler un numéro d\'urgence."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Appuyez sur \"Menu\" pour déverrouiller l\'appareil."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">Pendant %d h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarme suivante)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Jusqu\'à la désactivation"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Jusqu\'à ce que vous désactiviez le mode « Ne pas déranger »"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 15536cf..9f08389 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Le code PIN est erroné."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Pour déverrouiller le clavier, appuyez sur \"Menu\" puis sur 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numéro d\'urgence"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Aucun service"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Aucun service"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Écran verrouillé"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Appuyez sur \"Menu\" pour déverrouiller le téléphone ou appeler un numéro d\'urgence"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Appuyez sur \"Menu\" pour déverrouiller le téléphone."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">Pendant %d h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarme suivante)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Jusqu\'à la désactivation"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Jusqu\'à ce que vous désactiviez la fonctionnalité \"Ne pas déranger\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml
index c32add0..21e6ee7 100644
--- a/core/res/res/values-gl-rES/strings.xml
+++ b/core/res/res/values-gl-rES/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Código PIN incorrecto"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, preme Menú e, a continuación, 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emerxencia"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Non hai servizo."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sen servizo"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Pantalla bloqueada"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Preme Menú para desbloquear ou realizar unha chamada de emerxencia."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Preme Menú para desbloquear."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Durante unha h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Ata as <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Ata as <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Ata que desactives isto"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Ata que desactives o modo Non molestar"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml
index 6281abf..6dc0371 100644
--- a/core/res/res/values-gu-rIN/strings.xml
+++ b/core/res/res/values-gu-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"ખોટો PIN કોડ."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"અનલૉક કરવા માટે, મેનૂ દબાવો તે પછી 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ઇમરજન્સિ નંબર"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"કોઈ સેવા નથી."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"કોઈ સેવા નથી"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"સ્ક્રીન લૉક કરી."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"અનલૉક કરવા માટે અથવા કટોકટીનો કૉલ કરવા માટે મેનૂ દબાવો."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"અનલૉક કરવા માટે મેનૂ દબાવો."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">%d કલાક માટે</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> સુધી"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (આગલા એલાર્મ) સુધી"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"તમે આ બંધ ન કરો ત્યાં સુધી"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"તમે ખલેલ પાડશો નહીં બંધ ન કરો ત્યાં સુધી"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 96eb95c..6b1a91f 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"गलत पिन कोड."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"अनलॉक करने के लिए, मेनू दबाएं और फिर 0 दबाएं."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"आपातकालीन नंबर"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"कोई सेवा नहीं."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"कोई सेवा नहीं"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"स्‍क्रीन लॉक की गई है."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"अनलॉक करने के लिए मेनू दबाएं या आपातलकालीन कॉल करें."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"अनलॉक करने के लिए मेनू दबाएं."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">%d घंटे के लिए</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> तक"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (अगले अलार्म) तक"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"जब तक आप इसे बंद नहीं कर देते"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"जब तक कि आप परेशान ना करें को बंद नहीं कर देते"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 9b741bc..67d7110 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -651,7 +651,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Netočan PIN kôd."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Za otključavanje pritisnite Izbornik pa 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Broj hitne službe"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Nema usluge."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Nema usluge"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Zaslon zaključan."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Pritisnite Izbornik za otključavanje ili pozivanje hitnih službi."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Pritisnite Izbornik za otključavanje."</string>
@@ -1487,6 +1487,7 @@
       <item quantity="other">%d h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sljedeći alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dok ne isključite"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dok ne isključite \"Ne uznemiravaj\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index b063f0c..7e1f55b 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Helytelen PIN kód."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"A feloldáshoz nyomja meg a Menü, majd a 0 gombot."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Segélyhívó szám"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Nincs szolgáltatás."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Nincs szolgáltatás"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"A képernyő le van zárva."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"A feloldáshoz vagy segélyhívás kezdeményezéséhez nyomja meg a Menü gombot."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"A feloldáshoz nyomja meg a Menü gombot."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 órán keresztül</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Eddig: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Eddig: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ez a következő riasztás)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Amíg ki nem kapcsolja ezt"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Amíg ki nem kapcsolja a „Ne zavarjanak” lehetőséget"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index a295306..9259c06 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Սխալ PIN ծածկագիր:"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Ապակողպման համար սեղմեք Ցանկ, ապա 0:"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Արտակարգ իրավիճակների հեռախոսահամար"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ծառայություն չկա:"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ծառայություն չկա"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Էկրանը կողպված է:"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Սեղմեք Ցանկ` ապակողպելու համար, կամ կատարեք արտակարգ իրավիճակների զանգ:"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Ապակողպելու համար սեղմեք Ցանկը:"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">%d ժամով</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Մինչև <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Մինչև ժ. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-ը (հաջորդ զարթուցիչը)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Քանի դեռ չեք անջատել"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Մինչև չանջատեք «Չանհանգստացնել» գործառույթը"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 27f26a2..0f6cd3e 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Kode PIN salah."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Untuk membuka, tekan Menu lalu 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nomor darurat"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Tidak ada layanan."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Tidak ada layanan"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Layar terkunci."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Tekan Menu untuk membuka atau melakukan panggilan darurat."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tekan Menu untuk membuka."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Selama 1 jam</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Hingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Hingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarm berikutnya)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hingga Anda menonaktifkan ini"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hingga Anda menonaktifkan status Jangan Ganggu"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml
index c09914b..29f7408 100644
--- a/core/res/res/values-is-rIS/strings.xml
+++ b/core/res/res/values-is-rIS/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Rangt PIN-númer."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Til að taka úr lás ýtirðu á valmyndartakkann og síðan 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Neyðarnúmer"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ekkert símasamband."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ekkert símasamband"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skjár læstur."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Ýttu á valmyndartakkann til að taka úr lás eða hringja neyðarsímtal."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Ýttu á valmyndartakkann til að taka úr lás."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">Í %d klst.</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Þangað til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (næsta viðvörun)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Þar til þú slekkur á þessu"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Þar til þú slekkur á „Ónáðið ekki“"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index a034a25..ab541ea 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Codice PIN errato."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Per sbloccare, premi Menu, poi 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numero di emergenza"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Nessun servizio."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Nessun servizio"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Schermo bloccato."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Premi Menu per sbloccare o effettuare chiamate di emergenza."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Premi Menu per sbloccare."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Per 1 ora</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Fino alle ore <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Fino alle ore <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (prossima sveglia)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Fino alla disattivazione"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Fino alla disattivazione di Non disturbare"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index a56332c..de19821 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"‏קוד PIN שגוי"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"כדי לבטל את הנעילה, לחץ על \'תפריט\' ולאחר מכן על 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"מספר חירום"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"אין שירות"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"אין שירות"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"המסך נעול."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"לחץ על \'תפריט\' כדי לבטל את הנעילה או כדי לבצע שיחת חירום."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"לחץ על \'תפריט\' כדי לבטל את הנעילה."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="one">למשך שעה אחת</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"עד <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"עד <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ההתראה הבאה)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"עד שתכבה"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"עד שתכבה את \'נא לא להפריע\'"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 135c7ea..45e82ac 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PINコードが正しくありません。"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"MENU、0キーでロック解除"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急通報番号"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"通信サービスはありません"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"通信サービスはありません"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"画面ロック中"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"MENUキーでロック解除(または緊急通報)"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"MENUキーでロック解除"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1時間</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>まで"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(次のアラーム)まで"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ユーザーがOFFにするまで"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"[通知を非表示]をOFFにするまで"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index df302f5..7900f83 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"არასწორი PIN კოდი."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"განბლოკვისათვის დააჭირეთ მენიუს და შემდეგ 0-ს."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"გადაუდებელი დახმარების ნომრები"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"სერვისი არ არის."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"სერვისი არ არის"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ეკრანი დაბლოკილია."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"განბლოკვისთვის ან გადაუდებელი ზარისთვის დააჭირეთ მენიუს."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"განბლოკვისთვის დააჭირეთ მენიუს."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 სთ.</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-მდე"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-მდე (შემდეგი მაღვიძარა)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"სანამ ამას გამორთავდეთ"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"სანამ გამორთავთ „არ შემაწუხოთ“ ფუნქციას"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml
index 7af4df0..23ec612 100644
--- a/core/res/res/values-kk-rKZ/strings.xml
+++ b/core/res/res/values-kk-rKZ/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Қате PIN код"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Бекітпесін ашу үшін Мәзір, одан кейін 0 пернесін түртіңіз."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Төтенше жағдай нөмірі"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Қызмет көрсетілмейді."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Қызмет жоқ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Экран бекітілген."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Бекітпесін ашу үшін немесе төтенше қоңырауды табу үшін Мәзір тармағын басыңыз."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Ашу үшін Мәзір пернесін басыңыз."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 сағат</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін (келесі дабыл)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Сіз осыны өшіргенше"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Өшірмейінше мазаламау"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 3a02e76..8ffa8b8 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"កូដ PIN មិន​ត្រឹមត្រូវ។"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"ដើម្បី​ដោះ​សោ​​ ចុច​ម៉ឺនុយ​ បន្ទាប់មក 0 ។"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"លេខ​ពេល​អាសន្ន"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"គ្មាន​សេវា"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"គ្មានសេវាទេ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ចាក់​អេក្រង់។"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"ចុច​ម៉ឺនុយ ដើម្បី​ដោះ​សោ​ ឬ​ហៅ​ពេល​អាសន្ន។"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"ចុច​ម៉ឺនុយ ដើម្បី​ដោះ​សោ។"</string>
@@ -1472,6 +1472,7 @@
       <item quantity="one">អស់រយៈពេល 1 ម៉ោង</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"រហូត​ដល់ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"រហូតដល់ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ម៉ោងរោទិ៍បន្ទាប់)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"រហូត​ដល់ពេល​​អ្នក​បិទ​វា"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"រហូតទាល់តែអ្នកបិទ កុំរំខាន"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml
index 0e98430..a840c7e 100644
--- a/core/res/res/values-kn-rIN/strings.xml
+++ b/core/res/res/values-kn-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"ತಪ್ಪಾದ ಪಿನ್‌ ಕೋಡ್."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"ಅನ್‌ಲಾಕ್ ಮಾಡಲು, ಮೆನು ನಂತರ 0 ಒತ್ತಿರಿ."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ತುರ್ತು ಸಂಖ್ಯೆ"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"ಸೇವೆ ಇಲ್ಲ."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"ಯಾವುದೇ ಸೇವೆಯಿಲ್ಲ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ಪರದೆ ಲಾಕ್ ಆಗಿದೆ."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಮೆನು ಒತ್ತಿರಿ ಇಲ್ಲವೇ ತುರ್ತು ಕರೆಯನ್ನು ಮಾಡಿ."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಮೆನು ಒತ್ತಿರಿ."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">%d ಗಂಟೆಗೆ</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ (ಮುಂದಿನ ಅಲಾರಮ್)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ನೀವಿದನ್ನು ಆಫ್‌ ಮಾಡುವವರೆಗೆ"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"ನೀವು ಆಫ್ ಮಾಡುವವರೆಗೂ ಅಡಚಣೆ ಮಾಡಬೇಡಿ"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index f5a6fa4..59b0214 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN 코드가 잘못되었습니다."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"잠금해제하려면 메뉴를 누른 다음 0을 누릅니다."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"비상 전화번호"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"서비스 불가"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"서비스 불가"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"화면 잠김"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"비상 전화를 걸거나 잠금해제하려면 메뉴를 누르세요."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"잠금해제하려면 메뉴를 누르세요."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1시간 동안</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>까지"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(다음 알람)까지"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"이 기능을 사용 중지할 때까지"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"알림 일시중지 기능을 사용 중지할 때까지"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml
index f20551d..59c34dc 100644
--- a/core/res/res/values-ky-rKG/strings.xml
+++ b/core/res/res/values-ky-rKG/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN-код туура эмес."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Кулпусун ачуу үчүн, Менюна андан соң 0 баскычын басыңыз."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Шашылыш чалуу номери"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Байланыш жок."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Байланыш жок"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Экран кулпуланды."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Кулпусун ачып же Шашылыш чалуу аткаруу үчүн менюну басыңыз."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Бөгөттөн чыгаруу үчүн Менюну басыңыз."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 саатка</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> чейин"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> чейин (кийинки ойготкуч)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Бул өчүрүлгөнгө чейин"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Тынчымды алба\" режими өчүрүлгөнгө чейин"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 2e58b116..f131914 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"ລະຫັດ PIN ບໍ່ຖືກຕ້ອງ."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"ເພື່ອປົດລັອກ, ໃຫ້ກົດເມນູ ແລ້ວກົດ 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ເບີໂທສຸກເສີນ"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"ບໍ່ມີບໍລິການ."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"ບໍ່ມີບໍລິການ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ລັອກໜ້າຈໍແລ້ວ."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"ກົດ ເມນູ ເພື່ອປົດລັອກ ຫຼື ໂທອອກຫາເບີສຸກເສີນ."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"ກົດ \"ເມນູ\" ເພື່ອປົດລັອກ."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">ເປັນ​ເວ​ລາ 1 ຊມ</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"ຈົນ​ຮອດ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"ຈົນ​ກ​່​ວາ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ສັນ​ຍານ​ເຕືອນ​ຕໍ່ໄປ​)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ຈົນກວ່າ​ທ່ານ​ຈະ​ປິດ​"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"ຈົນ​ກ່​ວາ​ທ່ານ​ປິດ​ຫ້າມ​ລົບ​ກວນ"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index eb2903f..536a107 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Neteisingas PIN kodas."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Jei norite atrakinti, paspauskite „Meniu“ ir 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Pagalbos numeris"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Nėra paslaugos."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Nėra paslaugos"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekranas užrakintas."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Paspauskite „Meniu“, kad atrakintumėte ar skambintumėte pagalbos numeriu."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Paspauskite „Meniu“, jei norite atrakinti."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="other">%d val.</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Iki <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Iki <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (kitas signalas)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Kol išjungsite"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Kol neišjungsite režimo „Netrukdyti“"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 7f10900..288493a 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -651,7 +651,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN kods nav pareizs."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Lai atbloķētu, nospiediet Izvēlne, pēc tam 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Ārkārtas numurs"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Nav pakalpojuma."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Nav pakalpojuma"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekrāns ir bloķēts."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Nospiediet Izvēlne, lai atbloķētu, vai veiciet ārkārtas zvanu."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Lai atbloķētu, nospiediet vienumu Izvēlne."</string>
@@ -1487,6 +1487,7 @@
       <item quantity="other">%d h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Līdz <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Līdz plkst. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nākamais signāls)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Līdz brīdim, kad izslēgsiet"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Līdz izslēgsiet statusu “Netraucēt”"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index fb639ca..0f39e42 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -44,14 +44,5 @@
         <item>false</item>
     </string-array>
 
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
-         Or string format of ApnSettingV3.
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string-array translatable="false" name="config_tether_apndata">
-        <item>[ApnSettingV3]SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,204,04,,DUN,,,true,0,,,,,,,gid,5A</item>
-    </string-array>
-
     <string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true;BAE0000000000000</string>
 </resources>
diff --git a/core/res/res/values-mcc302-mnc780/config.xml b/core/res/res/values-mcc302-mnc780/config.xml
index 51abd36..a48f695 100644
--- a/core/res/res/values-mcc302-mnc780/config.xml
+++ b/core/res/res/values-mcc302-mnc780/config.xml
@@ -21,25 +21,6 @@
      for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-      <item>1</item>
-      <item>4</item>
-      <item>7</item>
-      <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
-         Or string format of ApnSettingV3.
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string-array translatable="false" name="config_tether_apndata">
-      <item>SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,302,780,,DUN</item>
-    </string-array>
-
     <!-- Don't use roaming icon for considered operators -->
     <string-array translatable="false" name="config_operatorConsideredNonRoaming">
         <item>302</item>
diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml
index a41be56..eab72bf 100644
--- a/core/res/res/values-mk-rMK/strings.xml
+++ b/core/res/res/values-mk-rMK/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Погрешен ПИН код."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"За да го отклучите, притиснете „Мени“ и потоа „0“."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Број за итни случаи"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Нема услуга."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Нема услуга"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Екранот е заклучен."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Притисни „Мени“ да се отклучи или да направи итен повик."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Притиснете „Мени“ за да се отклучи."</string>
@@ -1472,6 +1472,7 @@
       <item quantity="other">За %d ч.</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (следниот аларм)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Додека не го исклучите"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Додека не го исклучите Не вознемирувај"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml
index 2bdc9c8..caaedda 100644
--- a/core/res/res/values-ml-rIN/strings.xml
+++ b/core/res/res/values-ml-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"പിൻ കോഡ് തെറ്റാണ്."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"അൺലോക്ക് ചെയ്യുന്നതിന് മെനു, 0 എന്നിവ അമർത്തുക."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"അടിയന്തര നമ്പർ"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"സേവനമില്ല"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"സേവനമില്ല"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"സ്‌ക്രീൻ ലോക്കുചെയ്‌തു."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"അൺലോക്ക് ചെയ്യുന്നതിനായി മെനു അമർത്തുക അല്ലെങ്കിൽ അടിയന്തര കോൾ വിളിക്കുക."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"അൺലോക്കുചെയ്യാൻ മെനു അമർത്തുക."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">ഒരു മണിക്കൂറത്തേക്ക്</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> വരെ"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> വരെ (അടുത്ത അലാറം)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"നിങ്ങൾ ഇത് ഓ‌ഫാക്കും വരെ"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\'ശല്ല്യപ്പെടുത്തരുത്\' ഓഫാക്കുന്നതുവരെ"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 3b5531d..c02760d 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Буруу PIN код."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Тайлах бол Цэсийг дараад 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Яаралтай дугаар"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Үйлчилгээ байхгүй."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Үйлчилгээ байхгүй"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Дэлгэц түгжигдсэн."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Яаралтай дуудлага хийх буюу эсвэл түгжээг тайлах бол цэсийг дарна уу."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Тайлах бол цэсийг дарна уу."</string>
@@ -1468,6 +1468,7 @@
       <item quantity="one">1 цагийн турш:</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> хүртэл"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> хүртэл (дараагийн сэрүүлэг)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Таныг унтраах хүртэл"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Бүү саад бол\"-ыг унтраах хүртэл"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml
index 5956634..d557661 100644
--- a/core/res/res/values-mr-rIN/strings.xml
+++ b/core/res/res/values-mr-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"अयोग्य पिन कोड."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"अनलॉक करण्यासाठी, मेनू दाबा नंतर 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"आणीबाणीचा नंबर"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"सेवा नाही."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"सेवा नाही"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"स्क्रीन लॉक केली."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"अनलॉक करण्‍यासाठी मेनू दाबा किंवा आणीबाणीचा कॉल करा."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"अनलॉक करण्यासाठी मेनू दाबा."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">%d तासासाठी</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत (पुढील अलार्म)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"आपण हे बंद करेपर्यंत"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"आपण बंद करेपर्यंत व्यत्यय आणू नका"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index b69010e..8e5113b 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Kod PIN salah."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Untuk membuka kunci, tekan Menu, kemudian 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nombor kecemasan"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Tiada perkhidmatan."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Tiada perkhidmatan"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skrin dikunci."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Tekan Menu untuk menyahsekat atau membuat panggilan kecemasan."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tekan Menu untuk membuka kunci."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Selama 1 jam</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Sehingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Sehingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (penggera akan datang)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Sehingga anda matikan"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hingga anda mematikan Jangan Ganggu"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml
index 41528d8..427e844 100644
--- a/core/res/res/values-my-rMM/strings.xml
+++ b/core/res/res/values-my-rMM/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"ပင်နံပါတ်မှားနေပါသည်"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"သော့ဖွင့်ရန် Menu ထိုနောက်0ကိုနှိပ်ပါ"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"အရေးပေါ်နံပါတ်"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"ဆားဗစ် မရှိပါ"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"ဝန်ဆောင်မှု မရှိပါ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"မျက်နှာပြင်အားသော့ချထားသည်"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"ဖွင့်ရန်သို့မဟုတ်အရေးပေါ်ခေါ်ဆိုခြင်းပြုလုပ်ရန် မီနူးကိုနှိပ်ပါ"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"မီးနူးကို နှိပ်ခြင်းဖြင့် သော့ဖွင့်ပါ"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">၁ နာရီအတွက်</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>အထိ"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> အထိ (လာမည့် နှိုးစက်)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"သင်က ဒါကို ပိတ်မပစ်သည့် အထိ"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"မနှောင့်ယှက်ရန် ကိုသင်ပိတ်သည်အထိ"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b43314b..e231e2a 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Feil personlig kode."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"For å låse opp, trykk på menyknappen og deretter 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ingen tjeneste."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ingen dekning"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skjermen er låst"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Trykk på menyknappen for å låse opp eller ringe et nødnummer."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Trykk på menyknappen for å låse opp."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">I én time</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (neste alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Inntil du slår av funksjonen"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Inntil du slår av Ikke forstyrr"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml
index ac52911..8181f6c 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"गलत PIN कोड।"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"अनलक गर्न मेनु थिच्नुहोस् र त्यसपछि ० थिच्नुहोस्।"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"आपतकालीन नम्बर"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"सेवा छैन।"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"कुनै सेवा छैन"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"स्क्रिन लक गरिएको।"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"अनलक वा आपतकालीन कल गर्न मेनु थिच्नुहोस्।"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"अनलक गर्न मेनु थिच्नुहोस्।"</string>
@@ -1476,6 +1476,7 @@
       <item quantity="one">१ घन्टाको लागि</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> सम्म"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (अर्को अलार्म) सम्म"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"तपाईँले यसलाई बन्द नगरेसम्म"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"तपाईँले बन्द नगरे सम्म बाधा नपुर्याउँनुहोस्"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index a850943..361a816 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Onjuiste pincode."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Druk op \'Menu\' en vervolgens op 0 om te ontgrendelen."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Alarmnummer"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Geen service"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Geen service"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Scherm vergrendeld."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Druk op \'Menu\' om te ontgrendelen of noodoproep te plaatsen."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Druk op \'Menu\' om te ontgrendelen."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Gedurende 1 u</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgend alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Totdat u dit uitschakelt"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Totdat u \'Niet storen\' uitschakelt"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml
index e1276f1..0b80c4a 100644
--- a/core/res/res/values-pa-rIN/strings.xml
+++ b/core/res/res/values-pa-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"ਗ਼ਲਤ PIN ਕੋਡ।"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"ਅਨਲੌਕ ਕਰਨ ਲਈ, ਪਹਿਲਾਂ ਮੀਨੂ ਫਿਰ 0 ਦਬਾਓ।"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ਐਮਰਜੈਂਸੀ ਨੰਬਰ"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"ਕੋਈ ਸੇਵਾ ਨਹੀਂ।"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"ਕੋਈ ਸੇਵਾ ਨਹੀਂ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ਸਕ੍ਰੀਨ ਲੌਕ ਕੀਤੀ।"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"ਅਨਲੌਕ ਕਰਨ ਲਈ ਮੀਨੂ ਦਬਾਓ ਜਾਂ ਐਮਰਜੈਂਸੀ ਕਾਲ ਕਰੋ।"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"ਅਨਲੌਕ ਕਰਨ ਲਈ ਮੀਨੂ ਦਬਾਓ।"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">%d ਘੰਟਿਆਂ ਲਈ</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ (ਅਗਲਾ ਅਲਾਰਮ)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਸਨੂੰ ਬੰਦ ਨਹੀਂ ਕਰਦੇ ਹੋ"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ ਨੂੰ ਬੰਦ ਨਹੀਂ ਕਰਦੇ ਹੋ"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 0ef89a6..d72281b 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Błędny kod PIN"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Aby odblokować, naciśnij Menu, a następnie 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numer alarmowy"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Brak usługi"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Brak usługi"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekran zablokowany."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Naciśnij Menu, aby odblokować lub wykonać połączenie alarmowe."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Naciśnij Menu, aby odblokować."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="one">1 godz.</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (następny alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dopóki nie wyłączysz"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Do wyłączenia Nie przeszkadzać"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 2f6c8bf..daf17ef 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Código PIN incorreto."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, pressione Menu e, em seguida, 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Sem serviço."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sem serviço"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Tela bloqueada."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Pressione Menu para desbloquear ou fazer uma chamada de emergência."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Pressione Menu para desbloquear."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">Por %d horas</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Até você desativar"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Até que você desative \"Não perturbe\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index e14303d..b98a1cd 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Código PIN incorreto."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, prima Menu e, em seguida, 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Nenhum serviço"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sem rede móvel"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ecrã bloqueado."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Prima Menu para desbloquear ou efectuar uma chamada de emergência."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Prima Menu para desbloquear."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Durante 1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Até que o utilizador desative"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Até desativar Não incomodar"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 2f6c8bf..daf17ef 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Código PIN incorreto."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, pressione Menu e, em seguida, 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Sem serviço."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sem serviço"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Tela bloqueada."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Pressione Menu para desbloquear ou fazer uma chamada de emergência."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Pressione Menu para desbloquear."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">Por %d horas</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Até você desativar"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Até que você desative \"Não perturbe\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 1faa14f..6c576ad 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -651,7 +651,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Cod PIN incorect."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Pentru a debloca, apăsaţi Meniu, apoi 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Număr de urgență"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Fără serviciu."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Fără semnal"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ecranul este blocat."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Apăsați Meniu pentru a debloca sau pentru a efectua apeluri de urgență."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Apăsaţi Meniu pentru deblocare."</string>
@@ -1487,6 +1487,7 @@
       <item quantity="one">Pentru 1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (următoarea alarmă)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Până la dezactivare"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Până când dezactivați „Nu deranja”"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 0621bbe..15cbe4a 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Неверный PIN-код."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Для разблокировки нажмите \"Меню\", а затем 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Экстренная служба"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Нет сигнала"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Нет сигнала"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Экран заблокирован."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Нажмите \"Меню\", чтобы разблокировать экран или вызвать службу экстренной помощи."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Для разблокировки нажмите \"Меню\"."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="other">На %d часа</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Следующий сигнал в <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Пока я не отключу"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Пока вы не отключите режим \"Не беспокоить\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml
index 582f912..5e6cfcd 100644
--- a/core/res/res/values-si-rLK/strings.xml
+++ b/core/res/res/values-si-rLK/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"වැරදි PIN කේතයකි."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"අගුළු ඇරීමට, මෙනුව ඔබා පසුව 0 ද ඔබන්න."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"හදිසි ඇමතුම් අංකය"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"සේවාව නැත."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"සේවාව නැත"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"තිරය අගුළු දමා ඇත."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"අගුළු හැරීමට මෙනුව ඔබන්න හෝ හදිසි ඇමතුම ලබාගන්න."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"අගුළු හැරීමට මෙනු ඔබන්න."</string>
@@ -1472,6 +1472,7 @@
       <item quantity="other">පැය %d ක් සඳහා</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> තෙක්"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> තෙක් (ඊළඟ එලාමය)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ඔබ මෙය අක්‍රිය කරන තුරු"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"බාධා නොකරන්න ඔබ අක්‍රිය කරන තුරු"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 8432348..dd3cbca 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Nesprávny kód PIN."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Ak chcete telefón odomknúť, stlačte Menu a následne 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Číslo tiesňového volania"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Žiadny signál"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Žiadny signál"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Obrazovka je uzamknutá."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Ak chcete odomknúť telefón alebo uskutočniť tiesňové volanie, stlačte Menu."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Telefón odomknete stlačením tlačidla Menu."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="one">Na 1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ďalší budík)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dokým túto funkciu nevypnete"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dokým nevypnete stav Nerušiť"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index c5ca3c2..5c554ab 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Napačna koda PIN."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Če želite telefon odkleniti, pritisnite meni in nato 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Številka za klic v sili"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ni storitve."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ni signala"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Zaslon je zaklenjen."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Če želite odkleniti napravo ali opraviti klic v sili, pritisnite meni."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Če želite odkleniti, pritisnite meni."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="other">%d h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (naslednji alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dokler tega ne izklopite"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dokler ne izklopite načina »ne moti«"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml
index 7df0f86..45a4c68 100644
--- a/core/res/res/values-sq-rAL/strings.xml
+++ b/core/res/res/values-sq-rAL/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Kodi PIN është i pasaktë."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Për të shkyçur, shtyp \"Meny\" pastaj 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numri i urgjencës"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Nuk ka shërbim."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Nuk ka shërbim"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekrani është i kyçur."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Shtyp \"Meny\" për të shkyçur ose për të kryer telefonatë urgjence."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Shtyp \"Meny\" për të shkyçur."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Për 1 orë</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Deri në <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Deri në <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarmi tjetër)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Deri sa ta çaktivizosh këtë"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Deri sa të çaktivizosh gjendjen \"Mos shqetëso\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 5bb2d78..71538de 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -651,7 +651,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN кôд је нетачан."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Да бисте откључали, притисните „Мени“, а затим 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Број за хитне случајеве"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Нема услуге."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Мобилна мрежа није доступна"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Екран је закључан."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Притисните „Мени“ да бисте откључали телефон или упутите хитан позив."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Притисните „Мени“ за откључавање."</string>
@@ -1487,6 +1487,7 @@
       <item quantity="other">За %d с</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (следећи аларм)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Док не искључите"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Док не искључите режим Не узнемиравај"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index e803cbb..49a3862 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Fel PIN-kod."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Menu och sedan på 0 om du vill låsa upp."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nödsamtalsnummer"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ingen tjänst."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ingen tjänst"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skärmen har låsts."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Tryck på Menu om du vill låsa upp eller ringa nödsamtal."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tryck på Menu om du vill låsa upp."</string>
@@ -1182,7 +1182,7 @@
     <string name="shareactionprovider_share_with" msgid="806688056141131819">"Dela med"</string>
     <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Dela med <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="content_description_sliding_handle" msgid="415975056159262248">"Skärmlåsfunktion. Tryck och dra."</string>
-    <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Lås upp genom att dra."</string>
+    <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Lås upp genom att svepa."</string>
     <string name="keyboard_headset_required_to_hear_password" msgid="7011927352267668657">"Anslut mikrofonlurar om du vill att lösenordet ska läsas upp."</string>
     <string name="keyboard_password_character_no_headset" msgid="2859873770886153678">"Punkt."</string>
     <string name="action_bar_home_description" msgid="5293600496601490216">"Visa startsidan"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">I en 1 tim</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Till kl. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Till <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nästa alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Tills du inaktiverar detta"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Tills du inaktiverar Stör ej"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index af04a46..1d54bcf 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Msimbo wa PIN usio sahihi."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Ili kufungua, bofya Menyu kisha 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nambari ya dharura"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Hakuna huduma"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Hakuna huduma"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"skrini imefungwa."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Bonyeza Menyu ili kufungua au kupiga simu ya dharura."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Bonyeza Menyu ili kufungua."</string>
@@ -1472,6 +1472,7 @@
       <item quantity="one">Kwa saa 1 </item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Hadi <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Mpaka <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (kengele inayofuata)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hadi utakapozima hili"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hadi utakapozima Usinisumbue"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml
index 569fe82..e520908 100644
--- a/core/res/res/values-ta-rIN/strings.xml
+++ b/core/res/res/values-ta-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"தவறான பின் குறியீடு."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"தடைநீக்க, மெனுவை அழுத்தி பின்பு 0 ஐ அழுத்தவும்."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"அவசர எண்"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"சேவை இல்லை."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"சேவை இல்லை"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"திரை பூட்டப்பட்டுள்ளது."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"தடைநீக்க மெனுவை அழுத்தவும் அல்லது அவசர அழைப்பை மேற்கொள்ளவும்."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"திறக்க, மெனுவை அழுத்தவும்."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 மணிநேரத்திற்கு</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> வரை"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> மணி (அடுத்த அலாரம்) வரை"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"இதை முடக்கும்வரை"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"தொந்தரவு செய்ய வேண்டாம் என்பதை முடக்கும் வரை"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml
index 77588b3..fd3d5f2 100644
--- a/core/res/res/values-te-rIN/strings.xml
+++ b/core/res/res/values-te-rIN/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"చెల్లని పిన్‌ కోడ్."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"అన్‌లాక్ చేయడానికి, మెను ఆపై 0ని నొక్కండి."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"అత్యవసర నంబర్"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"సేవ లేదు."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"సేవ లేదు"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"స్క్రీన్ లాక్ చేయబడింది."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"అన్‌లాక్ చేయడానికి లేదా అత్యవసర కాల్ చేయడానికి మెను నొక్కండి."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"అన్‌లాక్ చేయడానికి మెను నొక్కండి."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 గం పాటు</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> వరకు"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (తదుపరి అలారం) వరకు"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"మీరు దీన్ని ఆఫ్ చేసే వరకు"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"మీరు అంతరాయం కలిగించవద్దు ఎంపిక ఆఫ్ చేసే వరకు"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index b2df636..4b4af30 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"รหัส PIN ไม่ถูกต้อง"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"หากต้องการปลดล็อก กด เมนู ตามด้วย 0"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"หมายเลขฉุกเฉิน"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"ไม่มีบริการ"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"ไม่มีบริการ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"หน้าจอถูกล็อก"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"กด เมนู เพื่อปลดล็อกหรือโทรฉุกเฉิน"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"กด เมนู เพื่อปลดล็อก"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">เป็นเวลา 1 ชม.</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"จนถึงเวลา <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"จนถึงเวลา <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (การปลุกครั้งถัดไป)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"จนกว่าคุณจะปิดฟังก์ชันนี้"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"จนกว่าคุณจะปิดห้ามรบกวน"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 78c9358c..afc71b9 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Maling PIN code."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Upang i-unlock, pindutin ang Menu pagkatapos ay 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Pang-emergency na numero"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Walang serbisyo."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Walang serbisyo"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Naka-lock ang screen."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Pindutin ang Menu upang i-unlock o magsagawa ng pang-emergency na tawag."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Pindutin ang Menu upang i-unlock."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">Sa loob ng %d na oras</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Hanggang <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Hanggang <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (susunod na alarm)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hanggang sa i-off mo ito"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hanggang sa i-off mo ang Huwag Istorbohin"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index bf046e3..7cbf3d2 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Yanlış PIN kodu."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Kilidi açmak için önce Menü\'ye, sonra 0\'a basın."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Acil durum numarası"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Hizmet yok."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Hizmet yok"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekran kilitli."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Kilidi açmak veya acil çağrı yapmak için Menü\'ye basın."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Kilidi açmak için Menü\'ye basın."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 saat için</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Şu saate kadar: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sonraki alarma) saatine kadar"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Siz bunu kapatana kadar"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Rahatsız Etmeyin ayarını kapatana kadar"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 1aa1a51..9c4f867 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -652,7 +652,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Неправильний PIN-код."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Щоб розбл., натисн. меню та 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Аварійний номер"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Зв’язку немає."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Немає зв’язку"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Екран заблоков."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Натис. меню, щоб розбл. чи зробити авар. виклик."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Натисн. меню, щоб розбл."</string>
@@ -1504,6 +1504,7 @@
       <item quantity="other">Протягом %d год</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (наступний будильник)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Доки ви не вимкнете"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Доки ввімкнено режим \"Не турбувати\""</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml
index e6e20f1..dd8aeca 100644
--- a/core/res/res/values-ur-rPK/strings.xml
+++ b/core/res/res/values-ur-rPK/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"‏غلط PIN کوڈ۔"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"غیر مقفل کرنے کیلئے، مینو پھر 0 دبائیں۔"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ہنگامی نمبر"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"کوئی سروس نہیں ہے۔"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"کوئی سروس نہیں ہے"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"اسکرین مقفل ہے۔"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"غیر مقفل کرنے کیلئے مینو دبائیں یا ہنگامی کال کریں۔"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"غیر مقفل کرنے کیلئے مینو دبائیں۔"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 گھنٹہ کیلئے</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک (اگلا الارم)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"جب تک آپ اسے آف نہ کر دیں"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"جب تک آپ ڈسڑب نہ کریں کو آف نہیں کر دیتے"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml
index b7ae992..ee7da4e 100644
--- a/core/res/res/values-uz-rUZ/strings.xml
+++ b/core/res/res/values-uz-rUZ/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Noto‘g‘ri PIN-kod."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Qulfni ochish uchun avval \"Menu\"ni, so‘ngra 0 raqamini bosing."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Favqulodda raqam"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Aloqa yo‘q."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Xizmat mavjud emas"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekran qulflangan."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Qulfdan chiqarish yoki favqulodda qo‘ng‘iroqni amalga oshirish uchun \"Menyu\"ni bosing."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Qulfni ochish uchun \"Menyu\"ga bosing."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 soat</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Ushbu vaqtgacha: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> gacha (keyingi signal)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Men o‘chirmaguncha"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"“Bezovta qilinmasin” rejimi o‘chirilmaguncha"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 83b0d18..5ae3e1e 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Mã PIN không chính xác."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Để mở khóa, hãy nhấn vào Menu sau đó nhấn 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Số khẩn cấp"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Không có dịch vụ nào."</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Không có dịch vụ nào"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Màn hình đã khóa."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Nhấn vào Menu để mở khóa hoặc thực hiện cuộc gọi khẩn cấp."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Nhấn vào Menu để mở khóa."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">Trong 1 giờ</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Cho đến <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Cho tới <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (cảnh báo tiếp theo)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Cho đến khi bạn tắt tính năng này"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Cho đến khi bạn tắt Đừng làm phiền"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 54100d5..602f607 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -650,7 +650,8 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN码有误。"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"要解锁,请先按 MENU 再按 0。"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"急救或报警电话"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"无服务。"</string>
+    <!-- no translation found for lockscreen_carrier_default (6169005837238288522) -->
+    <skip />
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"屏幕已锁定。"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"按 Menu 解锁或进行紧急呼救。"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"按 MENU 解锁。"</string>
@@ -1470,6 +1471,7 @@
       <item quantity="one">1 小时</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"直到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(闹钟下次响铃时)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"直到您将其关闭"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"直到您关闭“勿扰”模式"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 4c713c9..41952a7 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN 碼不正確。"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"如要解鎖,請按選單鍵,然後按 0。"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急電話號碼"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"沒有服務。"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"沒有服務"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"螢幕已鎖定。"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"按選單鍵解鎖或撥打緊急電話。"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"按選單鍵解鎖。"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">需時 1 小時</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"完成時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (下一次響鬧)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"直至您關閉這項設定"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"直至您關閉「請勿騷擾」功能"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 46f0d2c..48ea636 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN 碼不正確。"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"如要解鎖,請按 Menu 鍵,然後按 0。"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急電話號碼"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"沒有服務。"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"沒有服務"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"螢幕已鎖定。"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"按下 [Menu] 解鎖或撥打緊急電話。"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"按下 Menu 鍵解鎖。"</string>
@@ -1470,6 +1470,7 @@
       <item quantity="one">1 小時</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"結束時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> 為止 (下一個鬧鐘)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"手動關閉這項設定前一律啟用"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"直到您關閉「零打擾」模式"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 0ecd571..f597e5d 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Ikhodi ye-PIN engalungile!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Ukuvula, chofoza Menyu bese 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Inombolo ephuthumayo"</string>
-    <string name="lockscreen_carrier_default" msgid="8963839242565653192">"Ayikho isevisi"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ayikho isevisi"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Isikrini sivaliwe."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Chofoza Menyu ukuvula noma ukwenza ikholi ephuthumayo."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Chofoza Menyu ukuvula."</string>
@@ -1470,6 +1470,7 @@
       <item quantity="other">Ngamahora angu-%d</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Kuze kube ngu-<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Kuze kube ngu-<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (i-alamu elandelayo)"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Uze uvale lokhu"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Uze uvale ungaphazamisi"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a6eb68b..093ea80 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -323,7 +323,9 @@
         <attr name="windowBackground" format="reference" />
         <!-- Drawable to draw selectively within the inset areas when the windowBackground
              has been set to null. This protects against seeing visual garbage in the
-             surface when the app has not drawn any content into this area. -->
+             surface when the app has not drawn any content into this area. One example is
+             when the user is resizing a window of an activity that has
+             {@link android.R.attr#resizeableActivity} set for multi-window mode. -->
         <attr name="windowBackgroundFallback" format="reference" />
         <!-- Drawable to use as a frame around the window. -->
         <attr name="windowFrame" format="reference" />
@@ -1826,6 +1828,9 @@
         <enum name="KEYCODE_MEDIA_STEP_FORWARD" value="274" />
         <enum name="KEYCODE_MEDIA_STEP_BACKWARD" value="275" />
         <enum name="KEYCODE_SOFT_SLEEP" value="276" />
+        <enum name="KEYCODE_CUT" value="277" />
+        <enum name="KEYCODE_COPY" value="278" />
+        <enum name="KEYCODE_PASTE" value="279" />
     </attr>
 
     <!-- ***************************************************************** -->
@@ -7605,6 +7610,44 @@
         <attr name="pointerIconSpotTouch" format="reference" />
         <!-- Reference to a pointer icon drawable with STYLE_SPOT_ANCHOR -->
         <attr name="pointerIconSpotAnchor" format="reference" />
+        <!-- Reference to a pointer drawable with STYLE_CONTEXT_MENU -->
+        <attr name="pointerIconContextMenu" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_HAND -->
+        <attr name="pointerIconHand" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_HELP -->
+        <attr name="pointerIconHelp" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_CELL -->
+        <attr name="pointerIconCell" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_CROSSHAIR -->
+        <attr name="pointerIconCrosshair" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_TEXT -->
+        <attr name="pointerIconText" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_VERTICAL_TEXT -->
+        <attr name="pointerIconVerticalText" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_ALIAS -->
+        <attr name="pointerIconAlias" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_COPY -->
+        <attr name="pointerIconCopy" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_NODROP -->
+        <attr name="pointerIconNodrop" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_ALL_SCROLL -->
+        <attr name="pointerIconAllScroll" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_HORIZONTAL_DOUBLE_ARROW -->
+        <attr name="pointerIconHorizontalDoubleArrow" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_VERTICAL_DOUBLE_ARROW -->
+        <attr name="pointerIconVerticalDoubleArrow" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW -->
+        <attr name="pointerIconTopRightDiagonalDoubleArrow" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW -->
+        <attr name="pointerIconTopLeftDiagonalDoubleArrow" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_ZOOM_IN -->
+        <attr name="pointerIconZoomIn" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_ZOOM_OUT -->
+        <attr name="pointerIconZoomOut" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_GRAB -->
+        <attr name="pointerIconGrab" format="reference"/>
+        <!-- Reference to a pointer drawable with STYLE_GRABBING -->
+        <attr name="pointerIconGrabbing" format="reference"/>
     </declare-styleable>
 
     <declare-styleable name="PointerIcon">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 9d844a8..322ac4f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2197,18 +2197,25 @@
       <attr name="name" />
     </declare-styleable>
 
-    <!-- <code>initial-layout</code> tag allows configuring the initial layout for the activity
-         within multi-window environment. -->
-    <declare-styleable name="AndroidManifestInitialLayout" parent="AndroidManifestActivity">
+    <!-- <code>layout</code> tag allows configuring the layout for the activity within multi-window
+         environment. -->
+    <declare-styleable name="AndroidManifestLayout" parent="AndroidManifestActivity">
         <!-- Initial width of the activity. Can be either a fixed value or fraction, in which case
              the width will be constructed as a fraction of the total available width. -->
-        <attr name="activityWidth" format="dimension|fraction" />
+        <attr name="initialWidth" format="dimension|fraction" />
         <!-- Initial height of the activity. Can be either a fixed value or fraction, in which case
              the height will be constructed as a fraction of the total available height. -->
-        <attr name="activityHeight" format="dimension|fraction" />
+        <attr name="initialHeight" format="dimension|fraction" />
         <!-- Where to initially position the activity inside the available space. Uses constants
              defined in {@link android.view.Gravity}. -->
         <attr name="gravity" />
+        <!-- Minimal height of the activity.
+
+             <p>NOTE: A task's root activity value is applied to all additional activities launched
+             in the task. That is if the root activity of a task set minimal size, then the system
+             will set the same minimal size on all other activities in the task. It will also
+             ignore any other minimal size attributes of non-root activities. -->
+        <attr name="minimalSize" format="dimension" />
     </declare-styleable>
 
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6e956d7..74c745b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1249,6 +1249,9 @@
     <!-- Operating volatage for bluetooth controller. 0 by default-->
     <integer translatable="false" name="config_bluetooth_operating_voltage_mv">4</integer>
 
+    <!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
+    <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
+
     <!-- The default data-use polling period. -->
     <integer name="config_datause_polling_period_sec">600</integer>
 
@@ -2321,7 +2324,7 @@
 
     <!-- Flag indicating device support for EAP SIM, AKA, AKA' -->
     <bool name="config_eap_sim_based_auth_supported">true</bool>
- 
+
     <!-- How long history of previous vibrations should be kept for the dumpsys. -->
     <integer name="config_previousVibrationsDumpLimit">20</integer>
 
@@ -2352,4 +2355,12 @@
     <!-- Name of the component to handle network policy notifications. If present,
          disables NetworkPolicyManagerService's presentation of data-usage notifications. -->
     <string translatable="false" name="config_networkPolicyNotificationComponent"></string>
+
+    <!-- The fraction of display size (lower of height and width) that will be used to determine
+         the default minimal size for resizeable tasks. -->
+    <fraction name="config_displayFractionForDefaultMinimalSizeOfResizeableTask">25%</fraction>
+
+    <!-- The BT name of the keyboard packaged with the device. If this is defined, SystemUI will
+         automatically try to pair with it when the device exits tablet mode. -->
+    <string translatable="false" name="config_packagedKeyboardName"></string>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f4d0b39..c05b585 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2662,8 +2662,9 @@
 
     <public type="attr" name="listMenuViewStyle" />
     <public type="attr" name="subMenuArrow" />
-    <public type="attr" name="activityWidth" />
-    <public type="attr" name="activityHeight" />
+    <public type="attr" name="initialWidth" />
+    <public type="attr" name="initialHeight" />
+    <public type="attr" name="minimalSize" />
     <public type="attr" name="resizeableActivity" />
     <public type="attr" name="titleMargin" />
     <public type="attr" name="titleMarginStart" />
@@ -2676,6 +2677,7 @@
     <public type="attr" name="level" />
     <public type="attr" name="contextPopupMenuStyle" />
     <public type="attr" name="textAppearancePopupMenuHeader" />
+    <public type="attr" name="windowBackgroundFallback" />
 
     <public type="style" name="Theme.Material.DayNight" />
     <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 40cd097..c8d53d7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1697,7 +1697,7 @@
                                           --> <skip />
 
     <!-- On the keyguard screen, it shows the carrier the phone is connected to.  This is displayed if the phone is not connected to a carrier.-->
-    <string name="lockscreen_carrier_default">No service.</string>
+    <string name="lockscreen_carrier_default">No service</string>
 
     <!-- Shown in the lock screen to tell the user that the screen is locked. -->
     <string name="lockscreen_screen_locked">Screen locked.</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 11c4cc0..b831df8 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1336,6 +1336,33 @@
         <item name="pointerIconSpotHover">@drawable/pointer_spot_hover_icon</item>
         <item name="pointerIconSpotTouch">@drawable/pointer_spot_touch_icon</item>
         <item name="pointerIconSpotAnchor">@drawable/pointer_spot_anchor_icon</item>
+        <item name="pointerIconHand">@drawable/pointer_hand_icon</item>
+        <item name="pointerIconContextMenu">@drawable/pointer_context_menu_icon</item>
+        <item name="pointerIconHelp">@drawable/pointer_help_icon</item>
+        <item name="pointerIconCell">@drawable/pointer_cell_icon</item>
+        <item name="pointerIconCrosshair">@drawable/pointer_crosshair_icon</item>
+        <item name="pointerIconText">@drawable/pointer_text_icon</item>
+        <item name="pointerIconVerticalText">@drawable/pointer_vertical_text_icon</item>
+        <item name="pointerIconAlias">@drawable/pointer_alias_icon</item>
+        <item name="pointerIconCopy">@drawable/pointer_copy_icon</item>
+        <item name="pointerIconAllScroll">@drawable/pointer_all_scroll_icon</item>
+        <item name="pointerIconNodrop">@drawable/pointer_nodrop_icon</item>
+        <item name="pointerIconHorizontalDoubleArrow">
+            @drawable/pointer_horizontal_double_arrow_icon
+        </item>
+        <item name="pointerIconVerticalDoubleArrow">
+            @drawable/pointer_vertical_double_arrow_icon
+        </item>
+        <item name="pointerIconTopRightDiagonalDoubleArrow">
+            @drawable/pointer_top_right_diagonal_double_arrow_icon
+        </item>
+        <item name="pointerIconTopLeftDiagonalDoubleArrow">
+            @drawable/pointer_top_left_diagonal_double_arrow_icon
+        </item>
+        <item name="pointerIconZoomIn">@drawable/pointer_zoom_in_icon</item>
+        <item name="pointerIconZoomOut">@drawable/pointer_zoom_out_icon</item>
+        <item name="pointerIconGrab">@drawable/pointer_grab_icon</item>
+        <item name="pointerIconGrabbing">@drawable/pointer_grabbing_icon</item>
     </style>
 
     <!-- Wifi dialog styles -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 953b98f..3baddbb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -370,6 +370,7 @@
   <java-symbol type="integer" name="config_bluetooth_rx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_tx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
+  <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
   <java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -1686,6 +1687,7 @@
   <java-symbol type="id" name="replace_app_icon" />
   <java-symbol type="id" name="replace_message" />
   <java-symbol type="fraction" name="config_dimBehindFadeDuration" />
+  <java-symbol type="fraction" name="config_displayFractionForDefaultMinimalSizeOfResizeableTask" />
   <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" />
   <java-symbol type="integer" name="config_autoBrightnessBrighteningLightDebounce"/>
   <java-symbol type="integer" name="config_autoBrightnessDarkeningLightDebounce"/>
@@ -1955,6 +1957,7 @@
   <java-symbol type="id" name="close_window" />
   <java-symbol type="layout" name="non_client_decor_light" />
   <java-symbol type="layout" name="non_client_decor_dark" />
+  <java-symbol type="drawable" name="non_client_decor_title_focused" />
 
   <!-- From TelephonyProvider -->
   <java-symbol type="xml" name="apns" />
@@ -2329,4 +2332,6 @@
   <java-symbol type="drawable" name="platlogo_m" />
 
   <java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
+
+  <java-symbol type="string" name="config_packagedKeyboardName" />
 </resources>
diff --git a/core/tests/coretests/src/android/net/SntpClientTest.java b/core/tests/coretests/src/android/net/SntpClientTest.java
new file mode 100644
index 0000000..8b8cf67
--- /dev/null
+++ b/core/tests/coretests/src/android/net/SntpClientTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.SntpClient;
+import android.util.Log;
+import libcore.util.HexEncoding;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+
+public class SntpClientTest extends TestCase {
+    private static final String TAG = "SntpClientTest";
+
+    private static final int ORIGINATE_TIME_OFFSET = 24;
+    private static final int TRANSMIT_TIME_OFFSET = 40;
+
+    private static final int NTP_MODE_SERVER = 4;
+    private static final int NTP_MODE_BROADCAST = 5;
+
+    // From tcpdump (admittedly, an NTPv4 packet):
+    //
+    // Server, Leap indicator:  (0), Stratum 2 (secondary reference), poll 6 (64s), precision -20
+    // Root Delay: 0.005447, Root dispersion: 0.002716, Reference-ID: 221.253.71.41
+    //   Reference Timestamp:  3653932102.507969856 (2015/10/15 14:08:22)
+    //   Originator Timestamp: 3653932113.576327741 (2015/10/15 14:08:33)
+    //   Receive Timestamp:    3653932113.581012725 (2015/10/15 14:08:33)
+    //   Transmit Timestamp:   3653932113.581012725 (2015/10/15 14:08:33)
+    //     Originator - Receive Timestamp:  +0.004684958
+    //     Originator - Transmit Timestamp: +0.004684958
+    private static final String WORKING_VERSION4 =
+            "240206ec" +
+            "00000165" +
+            "000000b2" +
+            "ddfd4729" +
+            "d9ca9446820a5000" +
+            "d9ca9451938a3771" +
+            "d9ca945194bd3fff" +
+            "d9ca945194bd4001";
+
+    private final SntpTestServer mServer = new SntpTestServer();
+    private final SntpClient mClient = new SntpClient();
+
+    public void testBasicWorkingSntpClientQuery() throws Exception {
+        mServer.setServerReply(HexEncoding.decode(WORKING_VERSION4.toCharArray(), false));
+        assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500));
+        assertEquals(1, mServer.numRequestsReceived());
+        assertEquals(1, mServer.numRepliesSent());
+    }
+
+    public void testDnsResolutionFailure() throws Exception {
+        assertFalse(mClient.requestTime("ntp.server.doesnotexist.example", 5000));
+    }
+
+    public void testTimeoutFailure() throws Exception {
+        mServer.clearServerReply();
+        assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500));
+        assertEquals(1, mServer.numRequestsReceived());
+        assertEquals(0, mServer.numRepliesSent());
+    }
+
+    public void testIgnoreLeapNoSync() throws Exception {
+        final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+        reply[0] |= (byte) 0xc0;
+        mServer.setServerReply(reply);
+        assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500));
+        assertEquals(1, mServer.numRequestsReceived());
+        assertEquals(1, mServer.numRepliesSent());
+    }
+
+    public void testAcceptOnlyServerAndBroadcastModes() throws Exception {
+        final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+        for (int i = 0; i <= 7; i++) {
+            final String logMsg = "mode: " + i;
+            reply[0] &= (byte) 0xf8;
+            reply[0] |= (byte) i;
+            mServer.setServerReply(reply);
+            final boolean rval = mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500);
+            switch (i) {
+                case NTP_MODE_SERVER:
+                case NTP_MODE_BROADCAST:
+                    assertTrue(logMsg, rval);
+                    break;
+                default:
+                    assertFalse(logMsg, rval);
+                    break;
+            }
+            assertEquals(logMsg, 1, mServer.numRequestsReceived());
+            assertEquals(logMsg, 1, mServer.numRepliesSent());
+        }
+    }
+
+    public void testAcceptableStrataOnly() throws Exception {
+        final int STRATUM_MIN = 1;
+        final int STRATUM_MAX = 15;
+
+        final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+        for (int i = 0; i < 256; i++) {
+            final String logMsg = "stratum: " + i;
+            reply[1] = (byte) i;
+            mServer.setServerReply(reply);
+            final boolean rval = mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500);
+            if (STRATUM_MIN <= i && i <= STRATUM_MAX) {
+                assertTrue(logMsg, rval);
+            } else {
+                assertFalse(logMsg, rval);
+            }
+            assertEquals(logMsg, 1, mServer.numRequestsReceived());
+            assertEquals(logMsg, 1, mServer.numRepliesSent());
+        }
+    }
+
+    public void testZeroTransmitTime() throws Exception {
+        final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false);
+        Arrays.fill(reply, TRANSMIT_TIME_OFFSET, TRANSMIT_TIME_OFFSET + 8, (byte) 0x00);
+        mServer.setServerReply(reply);
+        assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500));
+        assertEquals(1, mServer.numRequestsReceived());
+        assertEquals(1, mServer.numRepliesSent());
+    }
+
+
+    private static class SntpTestServer {
+        private final Object mLock = new Object();
+        private final DatagramSocket mSocket;
+        private final InetAddress mAddress;
+        private final int mPort;
+        private byte[] mReply;
+        private int mRcvd;
+        private int mSent;
+        private Thread mListeningThread;
+
+        public SntpTestServer() {
+            mSocket = makeSocket();
+            mAddress = mSocket.getLocalAddress();
+            mPort = mSocket.getLocalPort();
+            Log.d(TAG, "testing server listening on (" + mAddress + ", " + mPort + ")");
+
+            mListeningThread = new Thread() {
+                public void run() {
+                    while (true) {
+                        byte[] buffer = new byte[512];
+                        DatagramPacket ntpMsg = new DatagramPacket(buffer, buffer.length);
+                        try {
+                            mSocket.receive(ntpMsg);
+                        } catch (IOException e) {
+                            Log.e(TAG, "datagram receive error: " + e);
+                            break;
+                        }
+                        synchronized (mLock) {
+                            mRcvd++;
+                            if (mReply == null) { continue; }
+                            // Copy transmit timestamp into originate timestamp.
+                            // TODO: bounds checking.
+                            System.arraycopy(ntpMsg.getData(), TRANSMIT_TIME_OFFSET,
+                                             mReply, ORIGINATE_TIME_OFFSET, 8);
+                            ntpMsg.setData(mReply);
+                            ntpMsg.setLength(mReply.length);
+                            try {
+                                mSocket.send(ntpMsg);
+                            } catch (IOException e) {
+                                Log.e(TAG, "datagram send error: " + e);
+                                break;
+                            }
+                            mSent++;
+                        }
+                    }
+                    mSocket.close();
+                }
+            };
+            mListeningThread.start();
+        }
+
+        private DatagramSocket makeSocket() {
+            DatagramSocket socket;
+            try {
+                socket = new DatagramSocket(0, InetAddress.getLoopbackAddress());
+            } catch (SocketException e) {
+                Log.e(TAG, "Failed to create test server socket: " + e);
+                return null;
+            }
+            return socket;
+        }
+
+        public void clearServerReply() {
+            setServerReply(null);
+        }
+
+        public void setServerReply(byte[] reply) {
+            synchronized (mLock) {
+                mReply = reply;
+                mRcvd = 0;
+                mSent = 0;
+            }
+        }
+
+        public InetAddress getAddress() { return mAddress; }
+        public int getPort() { return mPort; }
+        public int numRequestsReceived() { synchronized (mLock) { return mRcvd; } }
+        public int numRepliesSent() { synchronized (mLock) { return mSent; } }
+    }
+}
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f10ba96..2a10bdd 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -152,11 +152,11 @@
 # key 130 "KEY_PROPS"
 # key 131 "KEY_UNDO"
 # key 132 "KEY_FRONT"
-# key 133 "KEY_COPY"
+key 133   COPY
 # key 134 "KEY_OPEN"
-# key 135 "KEY_PASTE"
+key 135   PASTE
 # key 136 "KEY_FIND"
-# key 137 "KEY_CUT"
+key 137   CUT
 # key 138 "KEY_HELP"
 key 139   MENU
 key 140   CALCULATOR
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 11b4a9e..35182f9 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1335,7 +1335,7 @@
             return;
         }
         mLocales = new LocaleList(locale);
-        nSetTextLocale(mNativePaint, locale.toString());
+        nSetTextLocales(mNativePaint, locale.toString());
     }
 
     /**
@@ -1372,8 +1372,7 @@
         }
         if (locales.equals(mLocales)) return;
         mLocales = locales;
-        // TODO: Pass the whole LocaleList to native code
-        nSetTextLocale(mNativePaint, locales.getPrimary().toString());
+        nSetTextLocales(mNativePaint, locales.toLanguageTags());
     }
 
     /**
@@ -2715,8 +2714,8 @@
     private static native void nSetTextAlign(long paintPtr,
                                                    int align);
 
-    private static native void nSetTextLocale(long paintPtr,
-                                                    String locale);
+    private static native void nSetTextLocales(long paintPtr,
+                                                    String locales);
 
     private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
             char[] text, int index, int count, int contextIndex, int contextCount,
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index e235a99..3ed6a78 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -379,6 +379,7 @@
                 r, theme, attrs, R.styleable.AnimatedStateListDrawable);
         super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedStateListDrawable_visible);
         updateStateFromTypedArray(a);
+        updateDensity(r);
         a.recycle();
 
         inflateChildElements(r, parser, attrs, theme);
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 521c74b..6bf3afd 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -291,6 +291,7 @@
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable);
         super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible);
         updateStateFromTypedArray(a);
+        updateDensity(r);
         a.recycle();
 
         inflateChildElements(r, parser, attrs, theme);
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 1f7d996..ac72734 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -29,6 +29,7 @@
 import android.graphics.Rect;
 import android.graphics.PorterDuff.Mode;
 import android.os.SystemClock;
+import android.util.DisplayMetrics;
 import android.util.LayoutDirection;
 import android.util.SparseArray;
 import android.view.View;
@@ -581,6 +582,17 @@
         return mCurrDrawable;
     }
 
+    /**
+     * Updates the source density based on the resources used to inflate
+     * density-dependent values. Implementing classes should call this method
+     * during inflation.
+     *
+     * @param res the resources used to inflate density-dependent values
+     */
+    final void updateDensity(Resources res) {
+        mDrawableContainerState.updateDensity(res);
+    }
+
     @Override
     public void applyTheme(Theme theme) {
         mDrawableContainerState.applyTheme(theme);
@@ -638,22 +650,22 @@
      */
     public abstract static class DrawableContainerState extends ConstantState {
         final DrawableContainer mOwner;
-        final Resources mRes;
 
-        SparseArray<ConstantStateFuture> mDrawableFutures;
-
+        Resources mSourceRes;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
         int mChangingConfigurations;
         int mChildrenChangingConfigurations;
 
+        SparseArray<ConstantStateFuture> mDrawableFutures;
         Drawable[] mDrawables;
         int mNumChildren;
 
         boolean mVariablePadding = false;
-        boolean mPaddingChecked;
+        boolean mCheckedPadding;
         Rect mConstantPadding;
 
         boolean mConstantSize = false;
-        boolean mComputedConstantSize;
+        boolean mCheckedConstantSize;
         int mConstantWidth;
         int mConstantHeight;
         int mConstantMinimumWidth;
@@ -689,7 +701,13 @@
         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
                 Resources res) {
             mOwner = owner;
-            mRes = res != null ? res : orig != null ? orig.mRes : null;
+
+            final Resources sourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
+            mSourceRes = sourceRes;
+
+            final int densityDpi = sourceRes == null ? 0 : sourceRes.getDisplayMetrics().densityDpi;
+            final int sourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+            mDensity = sourceDensity;
 
             if (orig != null) {
                 mChangingConfigurations = orig.mChangingConfigurations;
@@ -713,21 +731,30 @@
                 mHasTintList = orig.mHasTintList;
                 mHasTintMode = orig.mHasTintMode;
 
-                // Cloning the following values may require creating futures.
-                mConstantPadding = orig.getConstantPadding();
-                mPaddingChecked = true;
+                if (orig.mDensity == sourceDensity) {
+                    if (orig.mCheckedPadding) {
+                        mConstantPadding = new Rect(orig.mConstantPadding);
+                        mCheckedPadding = true;
+                    }
 
-                mConstantWidth = orig.getConstantWidth();
-                mConstantHeight = orig.getConstantHeight();
-                mConstantMinimumWidth = orig.getConstantMinimumWidth();
-                mConstantMinimumHeight = orig.getConstantMinimumHeight();
-                mComputedConstantSize = true;
+                    if (orig.mCheckedConstantSize) {
+                        mConstantWidth = orig.mConstantWidth;
+                        mConstantHeight = orig.mConstantHeight;
+                        mConstantMinimumWidth = orig.mConstantMinimumWidth;
+                        mConstantMinimumHeight = orig.mConstantMinimumHeight;
+                        mCheckedConstantSize = true;
+                    }
+                }
 
-                mOpacity = orig.getOpacity();
-                mCheckedOpacity = true;
+                if (orig.mCheckedOpacity) {
+                    mOpacity = orig.mOpacity;
+                    mCheckedOpacity = true;
+                }
 
-                mStateful = orig.isStateful();
-                mCheckedStateful = true;
+                if (orig.mCheckedStateful) {
+                    mStateful = orig.mStateful;
+                    mCheckedStateful = true;
+                }
 
                 // Postpone cloning children and futures until we're absolutely
                 // sure that we're done computing values for the original state.
@@ -783,8 +810,8 @@
             mCheckedOpacity = false;
 
             mConstantPadding = null;
-            mPaddingChecked = false;
-            mComputedConstantSize = false;
+            mCheckedPadding = false;
+            mCheckedConstantSize = false;
 
             return pos;
         }
@@ -863,6 +890,31 @@
             return changed;
         }
 
+        /**
+         * Updates the source density based on the resources used to inflate
+         * density-dependent values.
+         *
+         * @param res the resources used to inflate density-dependent values
+         */
+        final void updateDensity(Resources res) {
+            if (mSourceRes != null) {
+                mSourceRes = res;
+            }
+
+            // The density may have changed since the last update (if any). Any
+            // dimension-type attributes will need their default values scaled.
+            final int densityDpi = res.getDisplayMetrics().densityDpi;
+            final int newSourceDensity = densityDpi == 0 ?
+                    DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+            final int oldSourceDensity = mDensity;
+            mDensity = newSourceDensity;
+
+            if (oldSourceDensity != newSourceDensity) {
+                mCheckedConstantSize = false;
+                mCheckedPadding = false;
+            }
+        }
+
         final void applyTheme(Theme theme) {
             if (theme != null) {
                 createAllFutures();
@@ -877,6 +929,8 @@
                         mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
                     }
                 }
+
+                updateDensity(theme.getResources());
             }
         }
 
@@ -941,7 +995,7 @@
                 return null;
             }
 
-            if ((mConstantPadding != null) || mPaddingChecked) {
+            if ((mConstantPadding != null) || mCheckedPadding) {
                 return mConstantPadding;
             }
 
@@ -961,7 +1015,7 @@
                 }
             }
 
-            mPaddingChecked = true;
+            mCheckedPadding = true;
             return (mConstantPadding = r);
         }
 
@@ -974,7 +1028,7 @@
         }
 
         public final int getConstantWidth() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -982,7 +1036,7 @@
         }
 
         public final int getConstantHeight() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -990,7 +1044,7 @@
         }
 
         public final int getConstantMinimumWidth() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -998,7 +1052,7 @@
         }
 
         public final int getConstantMinimumHeight() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -1006,7 +1060,7 @@
         }
 
         protected void computeConstantSize() {
-            mComputedConstantSize = true;
+            mCheckedConstantSize = true;
 
             createAllFutures();
 
@@ -1146,10 +1200,10 @@
              */
             public Drawable get(DrawableContainerState state) {
                 final Drawable result;
-                if (state.mRes == null) {
+                if (state.mSourceRes == null) {
                     result = mConstantState.newDrawable();
                 } else {
-                    result = mConstantState.newDrawable(state.mRes);
+                    result = mConstantState.newDrawable(state.mSourceRes);
                 }
                 result.setLayoutDirection(state.mLayoutDirection);
                 result.setCallback(state.mOwner);
diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java
index b01c643..4ce52d1 100644
--- a/graphics/java/android/graphics/drawable/LevelListDrawable.java
+++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java
@@ -88,7 +88,13 @@
     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
             throws XmlPullParserException, IOException {
         super.inflate(r, parser, attrs, theme);
+        updateDensity(r);
 
+        inflateChildElements(r, parser, attrs, theme);
+    }
+
+    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+            Theme theme) throws XmlPullParserException, IOException {
         int type;
 
         int low = 0;
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 758410a..64a9eb5 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -110,6 +110,7 @@
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
         super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
         updateStateFromTypedArray(a);
+        updateDensity(r);
         a.recycle();
 
         inflateChildElements(r, parser, attrs, theme);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 1105ca4..e161f3d 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -222,11 +222,14 @@
     // caching the bitmap by default is allowed.
     private boolean mAllowCaching = true;
 
+    /** The density of the display on which this drawable will be rendered. */
+    private int mTargetDensity;
+
     // Given the virtual display setup, the dpi can be different than the inflation's dpi.
     // Therefore, we need to scale the values we got from the getDimension*().
     private int mDpiScaledWidth = 0;
     private int mDpiScaledHeight = 0;
-    private Insets mDpiScaleInsets = Insets.NONE;
+    private Insets mDpiScaledInsets = Insets.NONE;
 
     // Temp variable, only for saving "new" operation at the draw() time.
     private final float[] mTmpFloats = new float[9];
@@ -234,17 +237,37 @@
     private final Rect mTmpBounds = new Rect();
 
     public VectorDrawable() {
-        this(null, null);
+        this(new VectorDrawableState(), null);
     }
 
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
     private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) {
-        if (state == null) {
-            mVectorState = new VectorDrawableState();
+        mVectorState = state;
+
+        updateLocalState(res);
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     *
+     * @param res resources of the context in which the drawable will be
+     *            displayed, or {@code null} to use the constant state defaults
+     */
+    private void updateLocalState(Resources res) {
+        if (res != null) {
+            final int densityDpi = res.getDisplayMetrics().densityDpi;
+            mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
         } else {
-            mVectorState = state;
-            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            mTargetDensity = mVectorState.mVPathRenderer.mSourceDensity;
         }
-        updateDimensionInfo(res, false);
+
+        mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
+        computeVectorSize();
     }
 
     @Override
@@ -416,55 +439,38 @@
     /** @hide */
     @Override
     public Insets getOpticalInsets() {
-        return mDpiScaleInsets;
+        return mDpiScaledInsets;
     }
 
     /*
-     * Update the VectorDrawable dimension since the res can be in different Dpi now.
-     * Basically, when a new instance is created or getDimension() is called, we should update
-     * the current VectorDrawable's dimension information.
-     * Only after updateStateFromTypedArray() is called, we should called this and update the
-     * constant state's dpi info, i.e. updateConstantStateDensity == true.
+     * Update local dimensions to adjust for a target density that may differ
+     * from the source density against which the constant state was loaded.
      */
-    void updateDimensionInfo(@Nullable Resources res, boolean updateConstantStateDensity) {
-        if (res != null) {
-            final int densityDpi = res.getDisplayMetrics().densityDpi;
-            final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+    void computeVectorSize() {
+        final VPathRenderer pathRenderer = mVectorState.mVPathRenderer;
+        final Insets opticalInsets = pathRenderer.mOpticalInsets;
 
-            if (updateConstantStateDensity) {
-                mVectorState.mVPathRenderer.mTargetDensity = targetDensity;
-            } else {
-                final int constantStateDensity = mVectorState.mVPathRenderer.mTargetDensity;
-                if (targetDensity != constantStateDensity && constantStateDensity != 0) {
-                    mDpiScaledWidth = Bitmap.scaleFromDensity(
-                            (int) mVectorState.mVPathRenderer.mBaseWidth, constantStateDensity,
-                            targetDensity);
-                    mDpiScaledHeight = Bitmap.scaleFromDensity(
-                            (int) mVectorState.mVPathRenderer.mBaseHeight,constantStateDensity,
-                            targetDensity);
-                    final int left = Bitmap.scaleFromDensity(
-                            mVectorState.mVPathRenderer.mOpticalInsets.left, constantStateDensity,
-                            targetDensity);
-                    final int right = Bitmap.scaleFromDensity(
-                            mVectorState.mVPathRenderer.mOpticalInsets.right, constantStateDensity,
-                            targetDensity);
-                    final int top = Bitmap.scaleFromDensity(
-                            mVectorState.mVPathRenderer.mOpticalInsets.top, constantStateDensity,
-                            targetDensity);
-                    final int bottom = Bitmap.scaleFromDensity(
-                            mVectorState.mVPathRenderer.mOpticalInsets.bottom, constantStateDensity,
-                            targetDensity);
-                    mDpiScaleInsets = Insets.of(left, top, right, bottom);
-                    return;
-                }
-            }
+        final int sourceDensity = pathRenderer.mSourceDensity;
+        final int targetDensity = mTargetDensity;
+        if (targetDensity != sourceDensity) {
+            mDpiScaledWidth = Bitmap.scaleFromDensity(
+                    (int) pathRenderer.mBaseWidth, sourceDensity, targetDensity);
+            mDpiScaledHeight = Bitmap.scaleFromDensity(
+                    (int) pathRenderer.mBaseHeight,sourceDensity, targetDensity);
+            final int left = Bitmap.scaleFromDensity(
+                    opticalInsets.left, sourceDensity, targetDensity);
+            final int right = Bitmap.scaleFromDensity(
+                    opticalInsets.right, sourceDensity, targetDensity);
+            final int top = Bitmap.scaleFromDensity(
+                    opticalInsets.top, sourceDensity, targetDensity);
+            final int bottom = Bitmap.scaleFromDensity(
+                    opticalInsets.bottom, sourceDensity, targetDensity);
+            mDpiScaledInsets = Insets.of(left, top, right, bottom);
+        } else {
+            mDpiScaledWidth = (int) pathRenderer.mBaseWidth;
+            mDpiScaledHeight = (int) pathRenderer.mBaseHeight;
+            mDpiScaledInsets = opticalInsets;
         }
-        // For all the other cases, like either res is null, constant state is not initialized or
-        // target density is the same as the constant state, we will just use the constant state
-        // dimensions.
-        mDpiScaledWidth = (int) mVectorState.mVPathRenderer.mBaseWidth;
-        mDpiScaledHeight = (int) mVectorState.mVPathRenderer.mBaseHeight;
-        mDpiScaleInsets = mVectorState.mVPathRenderer.mOpticalInsets;
     }
 
     @Override
@@ -487,7 +493,6 @@
             try {
                 state.mCacheDirty = true;
                 updateStateFromTypedArray(a);
-                updateDimensionInfo(t.getResources(), true /* update constant state */);
             } catch (XmlPullParserException e) {
                 throw new RuntimeException(e);
             } finally {
@@ -505,8 +510,8 @@
             path.applyTheme(t);
         }
 
-        // Update local state.
-        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+        // Update local properties.
+        updateLocalState(t.getResources());
     }
 
     /**
@@ -579,8 +584,8 @@
         state.mCacheDirty = true;
         inflateInternal(res, parser, attrs, theme);
 
-        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
-        updateDimensionInfo(res, true /* update constant state */);
+        // Update local properties.
+        updateLocalState(res);
     }
 
     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
@@ -593,6 +598,14 @@
         // Extract the theme attributes, if any.
         state.mThemeAttrs = a.extractThemeAttrs();
 
+        // The density may have changed since the last update (if any). Any
+        // dimension-type attributes will need their default values scaled.
+        final int densityDpi = a.getResources().getDisplayMetrics().densityDpi;
+        final int newSourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        final int oldSourceDensity = pathRenderer.mSourceDensity;
+        final float densityScale = newSourceDensity / (float) oldSourceDensity;
+        pathRenderer.mSourceDensity = newSourceDensity;
+
         final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
         if (tintMode != -1) {
             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
@@ -620,9 +633,11 @@
         }
 
         pathRenderer.mBaseWidth = a.getDimension(
-                R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth);
+                R.styleable.VectorDrawable_width,
+                pathRenderer.mBaseWidth * densityScale);
         pathRenderer.mBaseHeight = a.getDimension(
-                R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight);
+                R.styleable.VectorDrawable_height,
+                pathRenderer.mBaseHeight * densityScale);
 
         if (pathRenderer.mBaseWidth <= 0) {
             throw new XmlPullParserException(a.getPositionDescription() +
@@ -633,13 +648,17 @@
         }
 
         final int insetLeft = a.getDimensionPixelSize(
-                R.styleable.VectorDrawable_opticalInsetLeft, pathRenderer.mOpticalInsets.left);
+                R.styleable.VectorDrawable_opticalInsetLeft,
+                (int) (pathRenderer.mOpticalInsets.left * densityScale));
         final int insetTop = a.getDimensionPixelSize(
-                R.styleable.VectorDrawable_opticalInsetTop, pathRenderer.mOpticalInsets.top);
+                R.styleable.VectorDrawable_opticalInsetTop,
+                (int) (pathRenderer.mOpticalInsets.top * densityScale));
         final int insetRight = a.getDimensionPixelSize(
-                R.styleable.VectorDrawable_opticalInsetRight, pathRenderer.mOpticalInsets.right);
+                R.styleable.VectorDrawable_opticalInsetRight,
+                (int) (pathRenderer.mOpticalInsets.right * densityScale));
         final int insetBottom = a.getDimensionPixelSize(
-                R.styleable.VectorDrawable_opticalInsetBottom, pathRenderer.mOpticalInsets.bottom);
+                R.styleable.VectorDrawable_opticalInsetBottom,
+                (int) (pathRenderer.mOpticalInsets.bottom * densityScale));
         pathRenderer.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
 
         final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha,
@@ -917,7 +936,7 @@
         int mRootAlpha = 0xFF;
         String mRootName = null;
 
-        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+        int mSourceDensity = DisplayMetrics.DENSITY_DEFAULT;
 
         final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
 
@@ -954,7 +973,7 @@
             mChangingConfigurations = copy.mChangingConfigurations;
             mRootAlpha = copy.mRootAlpha;
             mRootName = copy.mRootName;
-            mTargetDensity = copy.mTargetDensity;
+            mSourceDensity = copy.mSourceDensity;
             if (copy.mRootName != null) {
                 mVGTargetsMap.put(copy.mRootName, this);
             }
@@ -1569,16 +1588,16 @@
         public boolean onStateChange(int[] stateSet) {
             boolean changed = false;
 
-            if (mStrokeColors != null && mStrokeColors.isStateful()) {
-                final int strokeColor = mStrokeColor;
-                mStrokeColor = mStrokeColors.getColorForState(stateSet, strokeColor);
-                changed |= strokeColor != mStrokeColor;
+            if (mStrokeColors != null) {
+                final int oldStrokeColor = mStrokeColor;
+                mStrokeColor = mStrokeColors.getColorForState(stateSet, oldStrokeColor);
+                changed |= oldStrokeColor != mStrokeColor;
             }
 
-            if (mFillColors != null && mFillColors.isStateful()) {
-                final int fillColor = mFillColor;
-                mFillColor = mFillColors.getColorForState(stateSet, fillColor);
-                changed |= fillColor != mFillColor;
+            if (mFillColors != null) {
+                final int oldFillColor = mFillColor;
+                mFillColor = mFillColors.getColorForState(stateSet, oldFillColor);
+                changed |= oldFillColor != mFillColor;
             }
 
             return changed;
@@ -1586,8 +1605,7 @@
 
         @Override
         public boolean isStateful() {
-            return mStrokeColors != null && mStrokeColors.isStateful()
-                    || mFillColors != null && mFillColors.isStateful();
+            return mStrokeColors != null || mFillColors != null;
         }
 
         @Override
@@ -1716,31 +1734,31 @@
             final ColorStateList fillColors = a.getColorStateList(
                     R.styleable.VectorDrawablePath_fillColor);
             if (fillColors != null) {
-                mFillColors = fillColors;
+                // If the color state list isn't stateful, discard the state
+                // list and keep the default (e.g. the only) color.
+                mFillColors = fillColors.isStateful() ? fillColors : null;
                 mFillColor = fillColors.getDefaultColor();
             }
 
             final ColorStateList strokeColors = a.getColorStateList(
                     R.styleable.VectorDrawablePath_strokeColor);
             if (strokeColors != null) {
-                mStrokeColors = strokeColors;
+                // If the color state list isn't stateful, discard the state
+                // list and keep the default (e.g. the only) color.
+                mStrokeColors = strokeColors.isStateful() ? strokeColors : null;
                 mStrokeColor = strokeColors.getDefaultColor();
             }
 
-            mFillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha,
-                    mFillAlpha);
+            mFillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, mFillAlpha);
             mStrokeLineCap = getStrokeLineCap(a.getInt(
                     R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
             mStrokeLineJoin = getStrokeLineJoin(a.getInt(
                     R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
             mStrokeMiterlimit = a.getFloat(
                     R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
-            mStrokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
-                    mStrokeAlpha);
-            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
-                    mStrokeWidth);
-            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
-                    mTrimPathEnd);
+            mStrokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha, mStrokeAlpha);
+            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
+            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
             mTrimPathOffset = a.getFloat(
                     R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
             mTrimPathStart = a.getFloat(
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 6f548bc..4385e70 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -27,6 +27,7 @@
     utils/LinearAllocator.cpp \
     utils/NinePatchImpl.cpp \
     utils/StringUtils.cpp \
+    utils/TestWindowContext.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
@@ -84,9 +85,7 @@
 hwui_cflags := \
     -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \
     -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \
-    -Wall -Wno-unused-parameter -Wunreachable-code \
-    -ffast-math -O3 -Werror
-
+    -Wall -Wno-unused-parameter -Wunreachable-code -Werror
 
 ifeq (true, $(HWUI_NEW_OPS))
     hwui_src_files += \
@@ -154,6 +153,27 @@
 include $(BUILD_STATIC_LIBRARY)
 
 # ------------------------
+# static library null gpu
+# ------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_MODULE := libhwui_static_null_gpu
+LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
+LOCAL_CFLAGS := \
+        $(hwui_cflags) \
+        -DHWUI_NULL_GPU
+LOCAL_SRC_FILES := \
+        $(hwui_src_files) \
+        tests/nullegl.cpp \
+        tests/nullgles.cpp
+LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(hwui_c_includes) $(call hwui_proto_include)
+
+include $(BUILD_STATIC_LIBRARY)
+
+# ------------------------
 # shared library
 # ------------------------
 
@@ -175,7 +195,7 @@
 LOCAL_MODULE := hwui_unit_tests
 LOCAL_MODULE_TAGS := tests
 LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
-LOCAL_STATIC_LIBRARIES := libhwui_static
+LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
 LOCAL_CFLAGS := $(hwui_cflags)
 
 LOCAL_SRC_FILES += \
@@ -210,22 +230,8 @@
 LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
 LOCAL_CFLAGS := $(hwui_cflags)
 
-HWUI_NULL_GPU := false
-
-ifeq (true, $(HWUI_NULL_GPU))
-    # Only need to specify the includes if we are not linking against
-    # libhwui_static as libhwui_static exports the appropriate includes
-    LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
-
-    LOCAL_SRC_FILES := \
-        $(hwui_src_files) \
-        tests/nullegl.cpp \
-        tests/nullgles.cpp
-
-    LOCAL_CFLAGS += -DHWUI_NULL_GPU
-else
-    LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
-endif
+# set to libhwui_static_null_gpu to skip actual GL commands
+LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
 
 LOCAL_SRC_FILES += \
     tests/TestContext.cpp \
@@ -250,10 +256,17 @@
 LOCAL_CFLAGS := $(hwui_cflags)
 LOCAL_C_INCLUDES += bionic/benchmarks/
 
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
+LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu
 LOCAL_STATIC_LIBRARIES := libbenchmark libbase
 
 LOCAL_SRC_FILES += \
-    microbench/DisplayListCanvasBench.cpp
+    microbench/DisplayListCanvasBench.cpp \
+    microbench/LinearAllocatorBench.cpp \
+    microbench/ShadowBench.cpp
+
+ifeq (true, $(HWUI_NEW_OPS))
+    LOCAL_SRC_FILES += \
+        microbench/OpReordererBench.cpp
+endif
 
 include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 4d9f9b4..94806ca 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -74,53 +74,64 @@
 #endif
 }
 
-void BakedOpRenderer::onRenderNodeOp(Info*, const RenderNodeOp&, const BakedOpState&) {
+void BakedOpRenderer::onRenderNodeOp(Info&, const RenderNodeOp&, const BakedOpState&) {
     LOG_ALWAYS_FATAL("unsupported operation");
 }
 
-void BakedOpRenderer::onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
-    info->caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
-    Texture* texture = info->getTexture(op.bitmap);
+void BakedOpRenderer::onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) {
+    info.caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
+    Texture* texture = info.getTexture(op.bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
     const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
             ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
     Glop glop;
-    GlopBuilder(info->renderState, info->caches, &glop)
+    GlopBuilder(info.renderState, info.caches, &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshTexturedUnitQuad(texture->uvMapper)
             .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
             .build();
-    info->renderGlop(state, glop);
+    info.renderGlop(state, glop);
 }
 
-void BakedOpRenderer::onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
+void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) {
     Glop glop;
-    GlopBuilder(info->renderState, info->caches, &glop)
+    GlopBuilder(info.renderState, info.caches, &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshUnitQuad()
             .setFillPaint(*op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewMapUnitToRect(op.unmappedBounds)
             .build();
-    info->renderGlop(state, glop);
+    info.renderGlop(state, glop);
 }
 
-void BakedOpRenderer::onSimpleRectsOp(Info* info, const SimpleRectsOp& op, const BakedOpState& state) {
+void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) {
     Glop glop;
-    GlopBuilder(info->renderState, info->caches, &glop)
+    GlopBuilder(info.renderState, info.caches, &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
             .setFillPaint(*op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewOffsetRect(0, 0, op.unmappedBounds)
             .build();
-    info->renderGlop(state, glop);
+    info.renderGlop(state, glop);
 }
 
+void BakedOpRenderer::onBeginLayerOp(Info& info, const BeginLayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
 
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index b8b4426..f45dbe4 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -65,7 +65,7 @@
      * These functions will perform the actual rendering of the individual operations in OpenGL,
      * given the transform/clip and other state built into the BakedOpState object passed in.
      */
-    #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info* info, const Type& op, const BakedOpState& state);
+    #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info& info, const Type& op, const BakedOpState& state);
     MAP_OPS(BAKED_OP_RENDERER_METHOD);
 };
 
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index e2201ca..ddb8c84 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -68,7 +68,7 @@
         // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
         clipRect = recordedOp.localClipRect;
         snapshot.transform->mapRect(clipRect);
-        clipRect.doIntersect(snapshot.getClipRect());
+        clipRect.doIntersect(snapshot.getRenderTargetClip());
         clipRect.snapToPixelBoundaries();
 
         // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index eca71c6..6a6cc42 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -259,7 +259,7 @@
     currentTransform()->mapRect(r);
     r.snapGeometryToPixelBoundaries(snapOut);
 
-    Rect clipRect(currentClipRect());
+    Rect clipRect(currentRenderTargetClip());
     clipRect.snapToPixelBoundaries();
 
     if (!clipRect.intersects(r)) return true;
@@ -287,7 +287,7 @@
     currentTransform()->mapRect(r);
     r.roundOut(); // rounded out to be conservative
 
-    Rect clipRect(currentClipRect());
+    Rect clipRect(currentRenderTargetClip());
     clipRect.snapToPixelBoundaries();
 
     if (!clipRect.intersects(r)) return true;
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index be57f44..4709ef4 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -147,7 +147,7 @@
     void setInvisible(bool value) { mSnapshot->invisible = value; }
 
     inline const mat4* currentTransform() const { return currentSnapshot()->transform; }
-    inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); }
+    inline const Rect& currentRenderTargetClip() const { return currentSnapshot()->getRenderTargetClip(); }
     inline Region* currentRegion() const { return currentSnapshot()->region; }
     inline int currentFlags() const { return currentSnapshot()->flags; }
     const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 2337299..59f0d7c 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -32,16 +32,27 @@
 namespace android {
 namespace uirenderer {
 
-DisplayListData::DisplayListData()
+DisplayList::DisplayList()
         : projectionReceiveIndex(-1)
+        , stdAllocator(allocator)
+        , chunks(stdAllocator)
+        , ops(stdAllocator)
+        , children(stdAllocator)
+        , bitmapResources(stdAllocator)
+        , pathResources(stdAllocator)
+        , patchResources(stdAllocator)
+        , paints(stdAllocator)
+        , regions(stdAllocator)
+        , referenceHolders(stdAllocator)
+        , functors(stdAllocator)
         , hasDrawOps(false) {
 }
 
-DisplayListData::~DisplayListData() {
+DisplayList::~DisplayList() {
     cleanupResources();
 }
 
-void DisplayListData::cleanupResources() {
+void DisplayList::cleanupResources() {
     if (CC_UNLIKELY(patchResources.size())) {
         ResourceCache& resourceCache = ResourceCache::getInstance();
         resourceCache.lock();
@@ -67,15 +78,10 @@
     regions.clear();
 }
 
-#if HWUI_NEW_OPS
-size_t DisplayListData::addChild(RenderNodeOp* op) {
-    mReferenceHolders.push_back(op->renderNode);
-#else
-size_t DisplayListData::addChild(DrawRenderNodeOp* op) {
-    mReferenceHolders.push_back(op->renderNode);
-#endif
-    size_t index = mChildren.size();
-    mChildren.push_back(op);
+size_t DisplayList::addChild(NodeOpType* op) {
+    referenceHolders.push_back(op->renderNode);
+    size_t index = children.size();
+    children.push_back(op);
     return index;
 }
 
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 8ba9ac3..86796c5 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -58,8 +58,14 @@
 #if HWUI_NEW_OPS
 struct RecordedOp;
 struct RenderNodeOp;
+
+typedef RecordedOp BaseOpType;
+typedef RenderNodeOp NodeOpType;
 #else
 class DrawRenderNodeOp;
+
+typedef DisplayListOp BaseOpType;
+typedef DrawRenderNodeOp NodeOpType;
 #endif
 
 /**
@@ -106,16 +112,16 @@
 /**
  * Data structure that holds the list of commands used in display list stream
  */
-class DisplayListData {
+class DisplayList {
     friend class DisplayListCanvas;
     friend class RecordingCanvas;
 public:
     struct Chunk {
-        // range of included ops in DLD::displayListOps
+        // range of included ops in DisplayList::ops()
         size_t beginOpIndex;
         size_t endOpIndex;
 
-        // range of included children in DLD::mChildren
+        // range of included children in DisplayList::children()
         size_t beginChildIndex;
         size_t endChildIndex;
 
@@ -123,42 +129,25 @@
         bool reorderChildren;
     };
 
-    DisplayListData();
-    ~DisplayListData();
-
-    // pointers to all ops within display list, pointing into allocator data
-    std::vector<DisplayListOp*> displayListOps;
+    DisplayList();
+    ~DisplayList();
 
     // index of DisplayListOp restore, after which projected descendents should be drawn
     int projectionReceiveIndex;
 
-    std::vector<const SkBitmap*> bitmapResources;
-    std::vector<const SkPath*> pathResources;
-    std::vector<const Res_png_9patch*> patchResources;
+    const LsaVector<Chunk>& getChunks() const { return chunks; }
+    const LsaVector<BaseOpType*>& getOps() const { return ops; }
 
-    std::vector<std::unique_ptr<const SkPaint>> paints;
-    std::vector<std::unique_ptr<const SkRegion>> regions;
-    Vector<Functor*> functors;
+    const LsaVector<NodeOpType*>& getChildren() const { return children; }
 
-    const std::vector<Chunk>& getChunks() const {
-            return chunks;
-    }
-#if HWUI_NEW_OPS
-    const std::vector<RecordedOp*>& getOps() const {
-        return ops;
-    }
-#endif
+    const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; }
+    const LsaVector<Functor*>& getFunctors() const { return functors; }
 
-#if HWUI_NEW_OPS
-    size_t addChild(RenderNodeOp* childOp);
-    const std::vector<RenderNodeOp*>& children() { return mChildren; }
-#else
-    size_t addChild(DrawRenderNodeOp* childOp);
-    const std::vector<DrawRenderNodeOp*>& children() { return mChildren; }
-#endif
+    size_t addChild(NodeOpType* childOp);
+
 
     void ref(VirtualLightRefBase* prop) {
-        mReferenceHolders.push_back(prop);
+        referenceHolders.push_back(prop);
     }
 
     size_t getUsedSize() {
@@ -169,23 +158,27 @@
     }
 
 private:
-#if HWUI_NEW_OPS
-    std::vector<RecordedOp*> ops;
-#endif
-
-    std::vector< sp<VirtualLightRefBase> > mReferenceHolders;
-
-#if HWUI_NEW_OPS
-    std::vector<RenderNodeOp*> mChildren;
-#else
-    // list of children display lists for quick, non-drawing traversal
-    std::vector<DrawRenderNodeOp*> mChildren;
-#endif
-
-    std::vector<Chunk> chunks;
-
-    // allocator into which all ops were allocated
+    // allocator into which all ops and LsaVector arrays allocated
     LinearAllocator allocator;
+    LinearStdAllocator<void*> stdAllocator;
+
+    LsaVector<Chunk> chunks;
+    LsaVector<BaseOpType*> ops;
+
+    // list of Ops referring to RenderNode children for quick, non-drawing traversal
+    LsaVector<NodeOpType*> children;
+
+    // Resources - Skia objects + 9 patches referred to by this DisplayList
+    LsaVector<const SkBitmap*> bitmapResources;
+    LsaVector<const SkPath*> pathResources;
+    LsaVector<const Res_png_9patch*> patchResources;
+    LsaVector<std::unique_ptr<const SkPaint>> paints;
+    LsaVector<std::unique_ptr<const SkRegion>> regions;
+    LsaVector< sp<VirtualLightRefBase> > referenceHolders;
+
+    // List of functors
+    LsaVector<Functor*> functors;
+
     bool hasDrawOps;
 
     void cleanupResources();
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 8d0ab03..bad3972 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -34,7 +34,7 @@
 DisplayListCanvas::DisplayListCanvas(int width, int height)
     : mState(*this)
     , mResourceCache(ResourceCache::getInstance())
-    , mDisplayListData(nullptr)
+    , mDisplayList(nullptr)
     , mTranslateX(0.0f)
     , mTranslateY(0.0f)
     , mHasDeferredTranslate(false)
@@ -45,14 +45,14 @@
 }
 
 DisplayListCanvas::~DisplayListCanvas() {
-    LOG_ALWAYS_FATAL_IF(mDisplayListData,
+    LOG_ALWAYS_FATAL_IF(mDisplayList,
             "Destroyed a DisplayListCanvas during a record!");
 }
 
 void DisplayListCanvas::reset(int width, int height) {
-    LOG_ALWAYS_FATAL_IF(mDisplayListData,
+    LOG_ALWAYS_FATAL_IF(mDisplayList,
             "prepareDirty called a second time during a recording!");
-    mDisplayListData = new DisplayListData();
+    mDisplayList = new DisplayList();
 
     mState.initializeSaveStack(width, height,
             0, 0, width, height, Vector3());
@@ -67,26 +67,26 @@
 // Operations
 ///////////////////////////////////////////////////////////////////////////////
 
-DisplayListData* DisplayListCanvas::finishRecording() {
+DisplayList* DisplayListCanvas::finishRecording() {
     flushRestoreToCount();
     flushTranslate();
 
     mPaintMap.clear();
     mRegionMap.clear();
     mPathMap.clear();
-    DisplayListData* data = mDisplayListData;
-    mDisplayListData = nullptr;
+    DisplayList* displayList = mDisplayList;
+    mDisplayList = nullptr;
     mSkiaCanvasProxy.reset(nullptr);
-    return data;
+    return displayList;
 }
 
 void DisplayListCanvas::callDrawGLFunction(Functor *functor) {
     addDrawOp(new (alloc()) DrawFunctorOp(functor));
-    mDisplayListData->functors.add(functor);
+    mDisplayList->functors.push_back(functor);
 }
 
 SkCanvas* DisplayListCanvas::asSkCanvas() {
-    LOG_ALWAYS_FATAL_IF(!mDisplayListData,
+    LOG_ALWAYS_FATAL_IF(!mDisplayList,
             "attempting to get an SkCanvas when we are not recording!");
     if (!mSkiaCanvasProxy) {
         mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this));
@@ -219,7 +219,7 @@
 void DisplayListCanvas::drawLayer(DeferredLayerUpdater* layerHandle) {
     // We ref the DeferredLayerUpdater due to its thread-safe ref-counting
     // semantics.
-    mDisplayListData->ref(layerHandle);
+    mDisplayList->ref(layerHandle);
     addDrawOp(new (alloc()) DrawLayerOp(layerHandle->backingLayer()));
 }
 
@@ -354,13 +354,13 @@
         CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom,
         CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry,
         CanvasPropertyPaint* paint) {
-    mDisplayListData->ref(left);
-    mDisplayListData->ref(top);
-    mDisplayListData->ref(right);
-    mDisplayListData->ref(bottom);
-    mDisplayListData->ref(rx);
-    mDisplayListData->ref(ry);
-    mDisplayListData->ref(paint);
+    mDisplayList->ref(left);
+    mDisplayList->ref(top);
+    mDisplayList->ref(right);
+    mDisplayList->ref(bottom);
+    mDisplayList->ref(rx);
+    mDisplayList->ref(ry);
+    mDisplayList->ref(paint);
     refBitmapsInShader(paint->value.getShader());
     addDrawOp(new (alloc()) DrawRoundRectPropsOp(&left->value, &top->value,
             &right->value, &bottom->value, &rx->value, &ry->value, &paint->value));
@@ -372,10 +372,10 @@
 
 void DisplayListCanvas::drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
         CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) {
-    mDisplayListData->ref(x);
-    mDisplayListData->ref(y);
-    mDisplayListData->ref(radius);
-    mDisplayListData->ref(paint);
+    mDisplayList->ref(x);
+    mDisplayList->ref(y);
+    mDisplayList->ref(radius);
+    mDisplayList->ref(paint);
     refBitmapsInShader(paint->value.getShader());
     addDrawOp(new (alloc()) DrawCirclePropsOp(&x->value, &y->value,
             &radius->value, &paint->value));
@@ -514,22 +514,26 @@
 }
 
 size_t DisplayListCanvas::addOpAndUpdateChunk(DisplayListOp* op) {
-    int insertIndex = mDisplayListData->displayListOps.size();
-    mDisplayListData->displayListOps.push_back(op);
+    int insertIndex = mDisplayList->ops.size();
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+#else
+    mDisplayList->ops.push_back(op);
+#endif
     if (mDeferredBarrierType != kBarrier_None) {
         // op is first in new chunk
-        mDisplayListData->chunks.emplace_back();
-        DisplayListData::Chunk& newChunk = mDisplayListData->chunks.back();
+        mDisplayList->chunks.emplace_back();
+        DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
         newChunk.beginOpIndex = insertIndex;
         newChunk.endOpIndex = insertIndex + 1;
         newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
 
-        int nextChildIndex = mDisplayListData->children().size();
+        int nextChildIndex = mDisplayList->children.size();
         newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
         mDeferredBarrierType = kBarrier_None;
     } else {
         // standard case - append to existing chunk
-        mDisplayListData->chunks.back().endOpIndex = insertIndex + 1;
+        mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
     }
     return insertIndex;
 }
@@ -552,22 +556,22 @@
         op->setQuickRejected(rejected);
     }
 
-    mDisplayListData->hasDrawOps = true;
+    mDisplayList->hasDrawOps = true;
     return flushAndAddOp(op);
 }
 
 size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
     int opIndex = addDrawOp(op);
 #if !HWUI_NEW_OPS
-    int childIndex = mDisplayListData->addChild(op);
+    int childIndex = mDisplayList->addChild(op);
 
     // update the chunk's child indices
-    DisplayListData::Chunk& chunk = mDisplayListData->chunks.back();
+    DisplayList::Chunk& chunk = mDisplayList->chunks.back();
     chunk.endChildIndex = childIndex + 1;
 
     if (op->renderNode->stagingProperties().isProjectionReceiver()) {
         // use staging property, since recording on UI thread
-        mDisplayListData->projectionReceiveIndex = opIndex;
+        mDisplayList->projectionReceiveIndex = opIndex;
     }
 #endif
     return opIndex;
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index 392bb3e..fc08504 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -66,8 +66,7 @@
     virtual ~DisplayListCanvas();
 
     void reset(int width, int height);
-
-    DisplayListData* finishRecording();
+    __attribute__((warn_unused_result)) DisplayList* finishRecording();
 
 // ----------------------------------------------------------------------------
 // HWUI Canvas state operations
@@ -237,7 +236,7 @@
     void flushTranslate();
     void flushReorderBarrier();
 
-    LinearAllocator& alloc() { return mDisplayListData->allocator; }
+    LinearAllocator& alloc() { return mDisplayList->allocator; }
 
     // Each method returns final index of op
     size_t addOpAndUpdateChunk(DisplayListOp* op);
@@ -254,7 +253,7 @@
     inline const T* refBuffer(const T* srcBuffer, int32_t count) {
         if (!srcBuffer) return nullptr;
 
-        T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T));
+        T* dstBuffer = (T*) mDisplayList->allocator.alloc(count * sizeof(T));
         memcpy(dstBuffer, srcBuffer, count * sizeof(T));
         return dstBuffer;
     }
@@ -269,7 +268,7 @@
         // The points/verbs within the path are refcounted so this copy operation
         // is inexpensive and maintains the generationID of the original path.
         const SkPath* cachedPath = new SkPath(*path);
-        mDisplayListData->pathResources.push_back(cachedPath);
+        mDisplayList->pathResources.push_back(cachedPath);
         return cachedPath;
     }
 
@@ -293,7 +292,7 @@
         if (cachedPaint == nullptr || *cachedPaint != *paint) {
             cachedPaint = new SkPaint(*paint);
             std::unique_ptr<const SkPaint> copy(cachedPaint);
-            mDisplayListData->paints.push_back(std::move(copy));
+            mDisplayList->paints.push_back(std::move(copy));
 
             // replaceValueFor() performs an add if the entry doesn't exist
             mPaintMap.replaceValueFor(key, cachedPaint);
@@ -313,7 +312,7 @@
         if (cachedRegion == nullptr) {
             std::unique_ptr<const SkRegion> copy(new SkRegion(*region));
             cachedRegion = copy.get();
-            mDisplayListData->regions.push_back(std::move(copy));
+            mDisplayList->regions.push_back(std::move(copy));
 
             // replaceValueFor() performs an add if the entry doesn't exist
             mRegionMap.replaceValueFor(region, cachedRegion);
@@ -329,12 +328,12 @@
         // which doesn't seem worth the extra cycles for this unlikely case.
         SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
         alloc().autoDestroy(localBitmap);
-        mDisplayListData->bitmapResources.push_back(localBitmap);
+        mDisplayList->bitmapResources.push_back(localBitmap);
         return localBitmap;
     }
 
     inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
-        mDisplayListData->patchResources.push_back(patch);
+        mDisplayList->patchResources.push_back(patch);
         mResourceCache.incrementRefcount(patch);
         return patch;
     }
@@ -344,7 +343,7 @@
     DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap;
 
     ResourceCache& mResourceCache;
-    DisplayListData* mDisplayListData;
+    DisplayList* mDisplayList;
 
     float mTranslateX;
     float mTranslateY;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 1f6282d..cb638a4 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1396,7 +1396,7 @@
 
 class DrawRenderNodeOp : public DrawBoundedOp {
     friend class RenderNode; // grant RenderNode access to info of child
-    friend class DisplayListData; // grant DisplayListData access to info of child
+    friend class DisplayList; // grant DisplayList access to info of child
     friend class DisplayListCanvas;
 public:
     DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple)
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 1d8be2b..c1417c4 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -52,7 +52,8 @@
     const std::vector<BakedOpState*>& getOps() const { return mOps; }
 
     void dump() const {
-        ALOGD("    Batch %p, merging %d, bounds " RECT_STRING, this, mMerging, RECT_ARGS(mBounds));
+        ALOGD("    Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
+                this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds));
     }
 protected:
     batchid_t mBatchId;
@@ -201,131 +202,11 @@
     Rect mClipRect;
 };
 
-class NullClient: public CanvasStateClient {
-    void onViewportInitialized() override {}
-    void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
-    GLuint getTargetFbo() const override { return 0; }
-};
-static NullClient sNullClient;
-
-OpReorderer::OpReorderer()
-        : mCanvasState(sNullClient) {
-}
-
-void OpReorderer::defer(int viewportWidth, int viewportHeight,
-        const std::vector< sp<RenderNode> >& nodes) {
-    mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
-            0, 0, viewportWidth, viewportHeight, Vector3());
-    for (const sp<RenderNode>& node : nodes) {
-        if (node->nothingToDraw()) continue;
-
-        // TODO: dedupe this code with onRenderNode()
-        mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
-        if (node->applyViewProperties(mCanvasState)) {
-            // not rejected do ops...
-            const DisplayListData& data = node->getDisplayListData();
-            deferImpl(data.getChunks(), data.getOps());
-        }
-        mCanvasState.restore();
-    }
-}
-
-void OpReorderer::defer(int viewportWidth, int viewportHeight,
-        const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops) {
-    ATRACE_NAME("prepare drawing commands");
-    mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
-            0, 0, viewportWidth, viewportHeight, Vector3());
-    deferImpl(chunks, ops);
-}
-
-/**
- * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
- *
- * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
- * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
- */
-#define OP_RECIEVER(Type) \
-        [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
-void OpReorderer::deferImpl(const std::vector<DisplayListData::Chunk>& chunks,
-        const std::vector<RecordedOp*>& ops) {
-    static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
-        MAP_OPS(OP_RECIEVER)
-    };
-    for (const DisplayListData::Chunk& chunk : chunks) {
-        for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
-            const RecordedOp* op = ops[opIndex];
-            receivers[op->opId](*this, *op);
-        }
-    }
-}
-
-void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
-    ATRACE_NAME("flush drawing commands");
-    for (const BatchBase* batch : mBatches) {
-        // TODO: different behavior based on batch->isMerging()
-        for (const BakedOpState* op : batch->getOps()) {
-            receivers[op->op->opId](arg, *op->op, *op);
-        }
-    }
-}
-
-BakedOpState* OpReorderer::bakeOpState(const RecordedOp& recordedOp) {
-    return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
-}
-
-void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
-    if (op.renderNode->nothingToDraw()) {
-        return;
-    }
-    mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
-
-    // apply state from RecordedOp
-    mCanvasState.concatMatrix(op.localMatrix);
-    mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
-            op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
-
-    // apply RenderProperties state
-    if (op.renderNode->applyViewProperties(mCanvasState)) {
-        // not rejected do ops...
-        const DisplayListData& data = op.renderNode->getDisplayListData();
-        deferImpl(data.getChunks(), data.getOps());
-    }
-    mCanvasState.restore();
-}
-
-static batchid_t tessellatedBatchId(const SkPaint& paint) {
-    return paint.getPathEffect()
-            ? OpBatchType::AlphaMaskTexture
-            : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
-}
-
-void OpReorderer::onBitmapOp(const BitmapOp& op) {
-    BakedOpState* bakedStateOp = bakeOpState(op);
-    if (!bakedStateOp) return; // quick rejected
-
-    mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
-    // TODO: AssetAtlas
-
-    deferMergeableOp(bakedStateOp, OpBatchType::Bitmap, mergeId);
-}
-
-void OpReorderer::onRectOp(const RectOp& op) {
-    BakedOpState* bakedStateOp = bakeOpState(op);
-    if (!bakedStateOp) return; // quick rejected
-    deferUnmergeableOp(bakedStateOp, tessellatedBatchId(*op.paint));
-}
-
-void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
-    BakedOpState* bakedStateOp = bakeOpState(op);
-    if (!bakedStateOp) return; // quick rejected
-    deferUnmergeableOp(bakedStateOp, OpBatchType::Vertices);
-}
-
 // iterate back toward target to see if anything drawn since should overlap the new op
 // if no target, merging ops still interate to find similar batch to insert after
-void OpReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
+void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
         BatchBase** targetBatch, size_t* insertBatchIndex) const {
-    for (size_t i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
+    for (int i = mBatches.size() - 1; i >= 0; i--) {
         BatchBase* overBatch = mBatches[i];
 
         if (overBatch == *targetBatch) break;
@@ -346,7 +227,8 @@
     }
 }
 
-void OpReorderer::deferUnmergeableOp(BakedOpState* op, batchid_t batchId) {
+void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
+        BakedOpState* op, batchid_t batchId) {
     OpBatch* targetBatch = mBatchLookup[batchId];
 
     size_t insertBatchIndex = mBatches.size();
@@ -359,7 +241,7 @@
         targetBatch->batchOp(op);
     } else  {
         // new non-merging batch
-        targetBatch = new (mAllocator) OpBatch(batchId, op);
+        targetBatch = new (allocator) OpBatch(batchId, op);
         mBatchLookup[batchId] = targetBatch;
         mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
     }
@@ -367,12 +249,13 @@
 
 // insertion point of a new batch, will hopefully be immediately after similar batch
 // (generally, should be similar shader)
-void OpReorderer::deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
+void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
+        BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
     MergingOpBatch* targetBatch = nullptr;
 
     // Try to merge with any existing batch with same mergeId
-    auto getResult = mMergingBatches[batchId].find(mergeId);
-    if (getResult != mMergingBatches[batchId].end()) {
+    auto getResult = mMergingBatchLookup[batchId].find(mergeId);
+    if (getResult != mMergingBatchLookup[batchId].end()) {
         targetBatch = getResult->second;
         if (!targetBatch->canMergeWith(op)) {
             targetBatch = nullptr;
@@ -387,18 +270,184 @@
         targetBatch->mergeOp(op);
     } else  {
         // new merging batch
-        targetBatch = new (mAllocator) MergingOpBatch(batchId, op);
-        mMergingBatches[batchId].insert(std::make_pair(mergeId, targetBatch));
+        targetBatch = new (allocator) MergingOpBatch(batchId, op);
+        mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
 
         mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
     }
 }
 
-void OpReorderer::dump() {
+void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const {
+    for (const BatchBase* batch : mBatches) {
+        // TODO: different behavior based on batch->isMerging()
+        for (const BakedOpState* op : batch->getOps()) {
+            receivers[op->op->opId](arg, *op->op, *op);
+        }
+    }
+}
+
+void OpReorderer::LayerReorderer::dump() const {
     for (const BatchBase* batch : mBatches) {
         batch->dump();
     }
 }
 
+OpReorderer::OpReorderer()
+        : mCanvasState(*this) {
+    mLayerReorderers.emplace_back();
+    mLayerStack.push_back(0);
+}
+
+void OpReorderer::onViewportInitialized() {}
+
+void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
+void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight,
+        const std::vector< sp<RenderNode> >& nodes) {
+    mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
+            clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
+            Vector3());
+    for (const sp<RenderNode>& node : nodes) {
+        if (node->nothingToDraw()) continue;
+
+        // TODO: dedupe this code with onRenderNode()
+        mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+        if (node->applyViewProperties(mCanvasState)) {
+            // not rejected do ops...
+            const DisplayList& displayList = node->getDisplayList();
+            deferImpl(displayList);
+        }
+        mCanvasState.restore();
+    }
+}
+
+void OpReorderer::defer(int viewportWidth, int viewportHeight, const DisplayList& displayList) {
+    ATRACE_NAME("prepare drawing commands");
+    mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
+            0, 0, viewportWidth, viewportHeight, Vector3());
+    deferImpl(displayList);
+}
+
+/**
+ * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
+ *
+ * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
+ * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
+ */
+#define OP_RECEIVER(Type) \
+        [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
+void OpReorderer::deferImpl(const DisplayList& displayList) {
+    static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
+        MAP_OPS(OP_RECEIVER)
+    };
+    for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
+        for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
+            const RecordedOp* op = displayList.getOps()[opIndex];
+            receivers[op->opId](*this, *op);
+        }
+    }
+}
+
+void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
+    ATRACE_NAME("flush drawing commands");
+    // Relay through layers in reverse order, since layers
+    // later in the list will be drawn by earlier ones
+    for (int i = mLayerReorderers.size() - 1; i >= 0; i--) {
+        mLayerReorderers[i].replayBakedOpsImpl(arg, receivers);
+    }
+}
+
+void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
+    if (op.renderNode->nothingToDraw()) {
+        return;
+    }
+    int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+
+    // apply state from RecordedOp
+    mCanvasState.concatMatrix(op.localMatrix);
+    mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
+            op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
+
+    // apply RenderProperties state
+    if (op.renderNode->applyViewProperties(mCanvasState)) {
+        // if node not rejected based on properties, do ops...
+        deferImpl(op.renderNode->getDisplayList());
+    }
+    mCanvasState.restoreToCount(count);
+}
+
+static batchid_t tessellatedBatchId(const SkPaint& paint) {
+    return paint.getPathEffect()
+            ? OpBatchType::AlphaMaskTexture
+            : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
+}
+
+void OpReorderer::onBitmapOp(const BitmapOp& op) {
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
+    if (!bakedStateOp) return; // quick rejected
+
+    mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
+    // TODO: AssetAtlas
+    currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
+}
+
+void OpReorderer::onRectOp(const RectOp& op) {
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
+    if (!bakedStateOp) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, tessellatedBatchId(*op.paint));
+}
+
+void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
+    if (!bakedStateOp) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
+}
+
+// TODO: test rejection at defer time, where the bounds become empty
+void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+    mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+    mCanvasState.writableSnapshot()->transform->loadIdentity();
+    mCanvasState.writableSnapshot()->initializeViewport(
+            (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight());
+    mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
+
+    // create a new layer, and push its index on the stack
+    mLayerStack.push_back(mLayerReorderers.size());
+    mLayerReorderers.emplace_back();
+    mLayerReorderers.back().beginLayerOp = &op;
+}
+
+void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
+    mCanvasState.restore();
+
+    const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
+
+    // pop finished layer off of the stack
+    int finishedLayerIndex = mLayerStack.back();
+    mLayerStack.pop_back();
+
+    // record the draw operation into the previous layer's list of draw commands
+    // uses state from the associated beginLayerOp, since it has all the state needed for drawing
+    LayerOp* drawLayerOp = new (mAllocator) LayerOp(
+            beginLayerOp.unmappedBounds,
+            beginLayerOp.localMatrix,
+            beginLayerOp.localClipRect,
+            beginLayerOp.paint);
+    BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+
+    if (bakedOpState) {
+        // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+        currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+    } else {
+        // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
+        mLayerReorderers[finishedLayerIndex].clear();
+        return;
+    }
+}
+
+void OpReorderer::onLayerOp(const LayerOp& op) {
+    LOG_ALWAYS_FATAL("unsupported");
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index b99172c..73dc9af 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -25,6 +25,8 @@
 #include <vector>
 #include <unordered_map>
 
+struct SkRect;
+
 namespace android {
 namespace uirenderer {
 
@@ -52,19 +54,63 @@
     };
 }
 
-class OpReorderer {
-public:
-    OpReorderer();
-
-    // TODO: not final, just presented this way for simplicity. Layers too?
-    void defer(int viewportWidth, int viewportHeight, const std::vector< sp<RenderNode> >& nodes);
-
-    void defer(int viewportWidth, int viewportHeight,
-            const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops);
+class OpReorderer : public CanvasStateClient {
     typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
 
     /**
-     * replayBakedOps() is templated based on what class will recieve ops being replayed.
+     * Stores the deferred render operations and state used to compute ordering
+     * for a single FBO/layer.
+     */
+    class LayerReorderer {
+    public:
+        // iterate back toward target to see if anything drawn since should overlap the new op
+        // if no target, merging ops still iterate to find similar batch to insert after
+        void locateInsertIndex(int batchId, const Rect& clippedBounds,
+                BatchBase** targetBatch, size_t* insertBatchIndex) const;
+
+        void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId);
+
+        // insertion point of a new batch, will hopefully be immediately after similar batch
+        // (generally, should be similar shader)
+        void deferMergeableOp(LinearAllocator& allocator,
+                BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
+
+        void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const;
+
+        void clear() {
+            mBatches.clear();
+        }
+
+        void dump() const;
+
+        const BeginLayerOp* beginLayerOp = nullptr;
+
+    private:
+        std::vector<BatchBase*> mBatches;
+
+        /**
+         * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
+         * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
+         * collide, which avoids the need to resolve mergeid collisions.
+         */
+        std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count];
+
+        // Maps batch ids to the most recent *non-merging* batch of that id
+        OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
+
+    };
+public:
+    OpReorderer();
+    virtual ~OpReorderer() {}
+
+    // TODO: not final, just presented this way for simplicity. Layers too?
+    void defer(const SkRect& clip, int viewportWidth, int viewportHeight,
+            const std::vector< sp<RenderNode> >& nodes);
+
+    void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+
+    /**
+     * replayBakedOps() is templated based on what class will receive ops being replayed.
      *
      * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
      * state->op->opId to lookup a receiver that will be called when the op is replayed.
@@ -75,22 +121,39 @@
      */
 #define BAKED_OP_RECEIVER(Type) \
     [](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
-        StaticReceiver::on##Type(static_cast<Arg*>(internalArg), static_cast<const Type&>(op), state); \
+        StaticReceiver::on##Type(*(static_cast<Arg*>(internalArg)), static_cast<const Type&>(op), state); \
     },
     template <typename StaticReceiver, typename Arg>
-    void replayBakedOps(Arg* arg) {
+    void replayBakedOps(Arg& arg) {
         static BakedOpReceiver receivers[] = {
             MAP_OPS(BAKED_OP_RECEIVER)
         };
-        StaticReceiver::startFrame(*arg);
-        replayBakedOpsImpl((void*)arg, receivers);
-        StaticReceiver::endFrame(*arg);
+        StaticReceiver::startFrame(arg);
+        replayBakedOpsImpl((void*)&arg, receivers);
+        StaticReceiver::endFrame(arg);
     }
-private:
-    BakedOpState* bakeOpState(const RecordedOp& recordedOp);
 
-    void deferImpl(const std::vector<DisplayListData::Chunk>& chunks,
-            const std::vector<RecordedOp*>& ops);
+    void dump() const {
+        for (auto&& layer : mLayerReorderers) {
+            layer.dump();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////
+    /// CanvasStateClient interface
+    ///////////////////////////////////////////////////////////////////
+    virtual void onViewportInitialized() override;
+    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
+    virtual GLuint getTargetFbo() const override { return 0; }
+
+private:
+    LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
+
+    BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
+        return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
+    }
+
+    void deferImpl(const DisplayList& displayList);
 
     void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
 
@@ -104,36 +167,27 @@
     void on##Type(const Type& op);
     MAP_OPS(INTERNAL_OP_HANDLER)
 
-    // iterate back toward target to see if anything drawn since should overlap the new op
-    // if no target, merging ops still iterate to find similar batch to insert after
-    void locateInsertIndex(int batchId, const Rect& clippedBounds,
-            BatchBase** targetBatch, size_t* insertBatchIndex) const;
+    // List of every deferred layer's render state. Replayed in reverse order to render a frame.
+    std::vector<LayerReorderer> mLayerReorderers;
 
-    void deferUnmergeableOp(BakedOpState* op, batchid_t batchId);
+    /*
+     * Stack of indices within mLayerReorderers representing currently active layers. If drawing
+     * layerA within a layerB, will contain, in order:
+     *  - 0 (representing FBO 0, always present)
+     *  - layerB's index
+     *  - layerA's index
+     *
+     * Note that this doesn't vector doesn't always map onto all values of mLayerReorderers. When a
+     * layer is finished deferring, it will still be represented in mLayerReorderers, but it's index
+     * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing
+     * ops added to it.
+    */
+    std::vector<size_t> mLayerStack;
 
-    // insertion point of a new batch, will hopefully be immediately after similar batch
-    // (generally, should be similar shader)
-    void deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
-
-    void dump();
-
-    std::vector<BatchBase*> mBatches;
-
-    /**
-     * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
-     * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
-     * collide, which avoids the need to resolve mergeid collisions.
-     */
-    std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatches[OpBatchType::Count];
-
-    // Maps batch ids to the most recent *non-merging* batch of that id
-    OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
     CanvasState mCanvasState;
 
     // contains ResolvedOps and Batches
     LinearAllocator mAllocator;
-
-    size_t mEarliestBatchIndex = 0;
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index cd03ac4..d4f65b6 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -223,7 +223,7 @@
 void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
     if (mState.currentlyIgnored()) return;
 
-    Rect clip(mState.currentClipRect());
+    Rect clip(mState.currentRenderTargetClip());
     clip.snapToPixelBoundaries();
 
     // Since we don't know what the functor will draw, let's dirty
@@ -488,7 +488,7 @@
     currentTransform()->mapRect(bounds);
 
     // Layers only make sense if they are in the framebuffer's bounds
-    bounds.doIntersect(mState.currentClipRect());
+    bounds.doIntersect(mState.currentRenderTargetClip());
     if (!bounds.isEmpty()) {
         // We cannot work with sub-pixels in this case
         bounds.snapToPixelBoundaries();
@@ -1036,7 +1036,7 @@
 }
 
 void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
-    bounds.doIntersect(mState.currentClipRect());
+    bounds.doIntersect(mState.currentRenderTargetClip());
     if (!bounds.isEmpty()) {
         bounds.snapToPixelBoundaries();
         android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
@@ -1084,7 +1084,7 @@
                 .setMeshIndexedQuads(&mesh[0], quadCount)
                 .setFillClear()
                 .setTransform(*currentSnapshot(), transformFlags)
-                .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getClipRect()))
+                .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getRenderTargetClip()))
                 .build();
         renderGlop(glop, GlopRenderType::LayerClear);
 
@@ -1099,7 +1099,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
-    const Rect& currentClip = mState.currentClipRect();
+    const Rect& currentClip = mState.currentRenderTargetClip();
     const mat4* currentMatrix = currentTransform();
 
     if (stateDeferFlags & kStateDeferFlag_Draw) {
@@ -1187,7 +1187,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::setScissorFromClip() {
-    Rect clip(mState.currentClipRect());
+    Rect clip(mState.currentRenderTargetClip());
     clip.snapToPixelBoundaries();
 
     if (mRenderState.scissor().set(clip.left, getViewportHeight() - clip.bottom,
@@ -1430,7 +1430,7 @@
             return;
         }
 
-        DeferredDisplayList deferredList(mState.currentClipRect());
+        DeferredDisplayList deferredList(mState.currentRenderTargetClip());
         DeferStateStruct deferStruct(deferredList, *this, replayFlags);
         renderNode->defer(deferStruct, 0);
 
@@ -1765,7 +1765,7 @@
     // No need to check against the clip, we fill the clip region
     if (mState.currentlyIgnored()) return;
 
-    Rect clip(mState.currentClipRect());
+    Rect clip(mState.currentRenderTargetClip());
     clip.snapToPixelBoundaries();
 
     SkPaint paint;
@@ -2030,7 +2030,7 @@
     }
     fontRenderer.setTextureFiltering(linearFilter);
 
-    const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip());
+    const Rect& clip(pureTranslate ? writableSnapshot()->getRenderTargetClip() : writableSnapshot()->getLocalClip());
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
@@ -2191,7 +2191,7 @@
     fontRenderer.setTextureFiltering(linearFilter);
 
     // TODO: Implement better clipping for scaled/rotated text
-    const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect();
+    const Rect* clip = !pureTranslate ? nullptr : &mState.currentRenderTargetClip();
     Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     bool status;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 36a8dac..c0c61db 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -29,7 +29,6 @@
 bool Properties::debugOverdraw = false;
 bool Properties::showDirtyRegions = false;
 bool Properties::skipEmptyFrames = true;
-bool Properties::swapBuffersWithDamage = true;
 bool Properties::useBufferAge = true;
 bool Properties::enablePartialUpdates = true;
 
@@ -117,7 +116,6 @@
     }
 
     skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
-    swapBuffersWithDamage = property_get_bool(PROPERTY_SWAP_WITH_DAMAGE, true);
     useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
     enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 3512c36..74cd74b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -142,13 +142,6 @@
 #define PROPERTY_SKIP_EMPTY_DAMAGE "debug.hwui.skip_empty_damage"
 
 /**
- * Setting this property will enable or disable usage of EGL_KHR_swap_buffers_with_damage
- * See: https://www.khronos.org/registry/egl/extensions/KHR/EGL_KHR_swap_buffers_with_damage.txt
- * Default is "true"
- */
-#define PROPERTY_SWAP_WITH_DAMAGE "debug.hwui.swap_with_damage"
-
-/**
  * Controls whether or not HWUI will use the EGL_EXT_buffer_age extension
  * to do partial invalidates. Setting this to "false" will fall back to
  * using BUFFER_PRESERVED instead
@@ -271,8 +264,6 @@
     static bool showDirtyRegions;
     // TODO: Remove after stabilization period
     static bool skipEmptyFrames;
-    // TODO: Remove after stabilization period
-    static bool swapBuffersWithDamage;
     static bool useBufferAge;
     static bool enablePartialUpdates;
 
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index a69f030..dd01637 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -41,7 +41,10 @@
         OP_FN(BitmapOp) \
         OP_FN(RectOp) \
         OP_FN(RenderNodeOp) \
-        OP_FN(SimpleRectsOp)
+        OP_FN(SimpleRectsOp) \
+        OP_FN(BeginLayerOp) \
+        OP_FN(EndLayerOp) \
+        OP_FN(LayerOp)
 
 // Generate OpId enum
 #define IDENTITY_FN(Type) Type,
@@ -112,6 +115,31 @@
     const size_t vertexCount;
 };
 
+/**
+ * Stateful operation! denotes the creation of an off-screen layer,
+ * and that commands following will render into it.
+ */
+struct BeginLayerOp : RecordedOp {
+    BeginLayerOp(BASE_PARAMS)
+            : SUPER(BeginLayerOp) {}
+};
+
+/**
+ * Stateful operation! Denotes end of off-screen layer, and that
+ * commands since last BeginLayerOp should be drawn into parent FBO.
+ *
+ * State in this op is empty, it just serves to signal that a layer has been finished.
+ */
+struct EndLayerOp : RecordedOp {
+    EndLayerOp()
+            : RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {}
+};
+
+struct LayerOp : RecordedOp {
+    LayerOp(BASE_PARAMS)
+            : SUPER(LayerOp) {}
+};
+
 }; // namespace uirenderer
 }; // namespace android
 
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index c4debd6..1f113bc 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -29,14 +29,14 @@
 }
 
 RecordingCanvas::~RecordingCanvas() {
-    LOG_ALWAYS_FATAL_IF(mDisplayListData,
+    LOG_ALWAYS_FATAL_IF(mDisplayList,
             "Destroyed a RecordingCanvas during a record!");
 }
 
 void RecordingCanvas::reset(int width, int height) {
-    LOG_ALWAYS_FATAL_IF(mDisplayListData,
+    LOG_ALWAYS_FATAL_IF(mDisplayList,
             "prepareDirty called a second time during a recording!");
-    mDisplayListData = new DisplayListData();
+    mDisplayList = new DisplayList();
 
     mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
 
@@ -45,18 +45,18 @@
     mRestoreSaveCount = -1;
 }
 
-DisplayListData* RecordingCanvas::finishRecording() {
+DisplayList* RecordingCanvas::finishRecording() {
     mPaintMap.clear();
     mRegionMap.clear();
     mPathMap.clear();
-    DisplayListData* data = mDisplayListData;
-    mDisplayListData = nullptr;
+    DisplayList* displayList = mDisplayList;
+    mDisplayList = nullptr;
     mSkiaCanvasProxy.reset(nullptr);
-    return data;
+    return displayList;
 }
 
 SkCanvas* RecordingCanvas::asSkCanvas() {
-    LOG_ALWAYS_FATAL_IF(!mDisplayListData,
+    LOG_ALWAYS_FATAL_IF(!mDisplayList,
             "attempting to get an SkCanvas when we are not recording!");
     if (!mSkiaCanvasProxy) {
         mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this));
@@ -73,6 +73,20 @@
 }
 
 // ----------------------------------------------------------------------------
+// CanvasStateClient implementation
+// ----------------------------------------------------------------------------
+
+void RecordingCanvas::onViewportInitialized() {
+
+}
+
+void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
+    if (removed.flags & Snapshot::kFlagIsFboLayer) {
+        addOp(new (alloc()) EndLayerOp());
+    }
+}
+
+// ----------------------------------------------------------------------------
 // android/graphics/Canvas state operations
 // ----------------------------------------------------------------------------
 // Save (layer)
@@ -97,8 +111,66 @@
 
 int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
         SkCanvas::SaveFlags flags) {
-    LOG_ALWAYS_FATAL("TODO");
-    return 0;
+    if (!(flags & SkCanvas::kClipToLayer_SaveFlag)) {
+        LOG_ALWAYS_FATAL("unclipped layers not supported");
+    }
+    // force matrix/clip isolation for layer
+    flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
+
+
+    const Snapshot& previous = *mState.currentSnapshot();
+
+    // initialize the snapshot as though it almost represents an FBO layer so deferred draw
+    // operations will be able to store and restore the current clip and transform info, and
+    // quick rejection will be correct (for display lists)
+
+    const Rect untransformedBounds(left, top, right, bottom);
+
+    // determine clipped bounds relative to previous viewport.
+    Rect visibleBounds = untransformedBounds;
+    previous.transform->mapRect(visibleBounds);
+
+
+    visibleBounds.doIntersect(previous.getRenderTargetClip());
+    visibleBounds.snapToPixelBoundaries();
+
+    Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
+    visibleBounds.doIntersect(previousViewport);
+
+    // Map visible bounds back to layer space, and intersect with parameter bounds
+    Rect layerBounds = visibleBounds;
+    Matrix4 inverse;
+    inverse.loadInverse(*previous.transform);
+    inverse.mapRect(layerBounds);
+    layerBounds.doIntersect(untransformedBounds);
+
+    int saveValue = mState.save((int) flags);
+    Snapshot& snapshot = *mState.writableSnapshot();
+
+    // layerBounds is now original bounds, but with clipped to clip
+    // and viewport to ensure it's minimal size.
+    if (layerBounds.isEmpty() || untransformedBounds.isEmpty()) {
+        // Don't bother recording layer, since it's been rejected
+        snapshot.resetClip(0, 0, 0, 0);
+        return saveValue;
+    }
+
+    snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
+    snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
+    snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+
+    Rect clip = layerBounds;
+    clip.translate(-untransformedBounds.left, -untransformedBounds.top);
+    snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom);
+    snapshot.roundRectClipState = nullptr;
+
+    addOp(new (alloc()) BeginLayerOp(
+            Rect(left, top, right, bottom),
+            *previous.transform, // transform to *draw* with
+            previous.getRenderTargetClip(), // clip to *draw* with
+            refPaint(paint)));
+
+    return saveValue;
 }
 
 // Matrix
@@ -187,7 +259,7 @@
 void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
     if (rects == nullptr) return;
 
-    Vertex* rectData = (Vertex*) mDisplayListData->allocator.alloc(vertexCount * sizeof(Vertex));
+    Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex));
     Vertex* vertex = rectData;
 
     float left = FLT_MAX;
@@ -345,36 +417,36 @@
             mState.getRenderTargetClipBounds(),
             renderNode);
     int opIndex = addOp(op);
-    int childIndex = mDisplayListData->addChild(op);
+    int childIndex = mDisplayList->addChild(op);
 
     // update the chunk's child indices
-    DisplayListData::Chunk& chunk = mDisplayListData->chunks.back();
+    DisplayList::Chunk& chunk = mDisplayList->chunks.back();
     chunk.endChildIndex = childIndex + 1;
 
     if (renderNode->stagingProperties().isProjectionReceiver()) {
         // use staging property, since recording on UI thread
-        mDisplayListData->projectionReceiveIndex = opIndex;
+        mDisplayList->projectionReceiveIndex = opIndex;
     }
 }
 
 size_t RecordingCanvas::addOp(RecordedOp* op) {
     // TODO: validate if "addDrawOp" quickrejection logic is useful before adding
-    int insertIndex = mDisplayListData->ops.size();
-    mDisplayListData->ops.push_back(op);
+    int insertIndex = mDisplayList->ops.size();
+    mDisplayList->ops.push_back(op);
     if (mDeferredBarrierType != kBarrier_None) {
         // op is first in new chunk
-        mDisplayListData->chunks.emplace_back();
-        DisplayListData::Chunk& newChunk = mDisplayListData->chunks.back();
+        mDisplayList->chunks.emplace_back();
+        DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
         newChunk.beginOpIndex = insertIndex;
         newChunk.endOpIndex = insertIndex + 1;
         newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
 
-        int nextChildIndex = mDisplayListData->children().size();
+        int nextChildIndex = mDisplayList->children.size();
         newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
         mDeferredBarrierType = kBarrier_None;
     } else {
         // standard case - append to existing chunk
-        mDisplayListData->chunks.back().endOpIndex = insertIndex + 1;
+        mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
     }
     return insertIndex;
 }
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index ed299e5..9c32b1a 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -41,7 +41,7 @@
     virtual ~RecordingCanvas();
 
     void reset(int width, int height);
-    DisplayListData* finishRecording();
+    __attribute__((warn_unused_result)) DisplayList* finishRecording();
 
 // ----------------------------------------------------------------------------
 // MISC HWUI OPERATIONS - TODO: CATEGORIZE
@@ -52,8 +52,8 @@
 // ----------------------------------------------------------------------------
 // CanvasStateClient interface
 // ----------------------------------------------------------------------------
-    virtual void onViewportInitialized() override {}
-    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override {}
+    virtual void onViewportInitialized() override;
+    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
     virtual GLuint getTargetFbo() const override { return -1; }
 
 // ----------------------------------------------------------------------------
@@ -187,7 +187,7 @@
 // ----------------------------------------------------------------------------
 // lazy object copy
 // ----------------------------------------------------------------------------
-    LinearAllocator& alloc() { return mDisplayListData->allocator; }
+    LinearAllocator& alloc() { return mDisplayList->allocator; }
 
     void refBitmapsInShader(const SkShader* shader);
 
@@ -195,7 +195,7 @@
     inline const T* refBuffer(const T* srcBuffer, int32_t count) {
         if (!srcBuffer) return nullptr;
 
-        T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T));
+        T* dstBuffer = (T*) mDisplayList->allocator.alloc(count * sizeof(T));
         memcpy(dstBuffer, srcBuffer, count * sizeof(T));
         return dstBuffer;
     }
@@ -210,7 +210,7 @@
         // The points/verbs within the path are refcounted so this copy operation
         // is inexpensive and maintains the generationID of the original path.
         const SkPath* cachedPath = new SkPath(*path);
-        mDisplayListData->pathResources.push_back(cachedPath);
+        mDisplayList->pathResources.push_back(cachedPath);
         return cachedPath;
     }
 
@@ -234,7 +234,7 @@
         if (cachedPaint == nullptr || *cachedPaint != *paint) {
             cachedPaint = new SkPaint(*paint);
             std::unique_ptr<const SkPaint> copy(cachedPaint);
-            mDisplayListData->paints.push_back(std::move(copy));
+            mDisplayList->paints.push_back(std::move(copy));
 
             // replaceValueFor() performs an add if the entry doesn't exist
             mPaintMap.replaceValueFor(key, cachedPaint);
@@ -254,7 +254,7 @@
         if (cachedRegion == nullptr) {
             std::unique_ptr<const SkRegion> copy(new SkRegion(*region));
             cachedRegion = copy.get();
-            mDisplayListData->regions.push_back(std::move(copy));
+            mDisplayList->regions.push_back(std::move(copy));
 
             // replaceValueFor() performs an add if the entry doesn't exist
             mRegionMap.replaceValueFor(region, cachedRegion);
@@ -270,12 +270,12 @@
         // which doesn't seem worth the extra cycles for this unlikely case.
         SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
         alloc().autoDestroy(localBitmap);
-        mDisplayListData->bitmapResources.push_back(localBitmap);
+        mDisplayList->bitmapResources.push_back(localBitmap);
         return localBitmap;
     }
 
     inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
-        mDisplayListData->patchResources.push_back(patch);
+        mDisplayList->patchResources.push_back(patch);
         mResourceCache.incrementRefcount(patch);
         return patch;
     }
@@ -288,7 +288,7 @@
     std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
     ResourceCache& mResourceCache;
     DeferredBarrierType mDeferredBarrierType = kBarrier_None;
-    DisplayListData* mDisplayListData = nullptr;
+    DisplayList* mDisplayList = nullptr;
     bool mHighContrastText = false;
     SkAutoTUnref<SkDrawFilter> mDrawFilter;
     int mRestoreSaveCount = -1;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d122a55..351fbaa 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -48,26 +48,26 @@
                 prefix, this, getName(), mLayer, mLayer->getFbo(),
                 mLayer->wasBuildLayered ? "true" : "false");
     }
-    if (mDisplayListData) {
-        for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
-            mDisplayListData->children()[i]->renderNode->debugDumpLayers(prefix);
+    if (mDisplayList) {
+        for (auto&& child : mDisplayList->getChildren()) {
+            child->renderNode->debugDumpLayers(prefix);
         }
     }
 }
 
 RenderNode::RenderNode()
         : mDirtyPropertyFields(0)
-        , mNeedsDisplayListDataSync(false)
-        , mDisplayListData(nullptr)
-        , mStagingDisplayListData(nullptr)
+        , mNeedsDisplayListSync(false)
+        , mDisplayList(nullptr)
+        , mStagingDisplayList(nullptr)
         , mAnimatorManager(*this)
         , mLayer(nullptr)
         , mParentCount(0) {
 }
 
 RenderNode::~RenderNode() {
-    deleteDisplayListData();
-    delete mStagingDisplayListData;
+    deleteDisplayList();
+    delete mStagingDisplayList;
     if (mLayer) {
         ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer);
         mLayer->postDecStrong();
@@ -75,10 +75,10 @@
     }
 }
 
-void RenderNode::setStagingDisplayList(DisplayListData* data) {
-    mNeedsDisplayListDataSync = true;
-    delete mStagingDisplayListData;
-    mStagingDisplayListData = data;
+void RenderNode::setStagingDisplayList(DisplayList* displayList) {
+    mNeedsDisplayListSync = true;
+    delete mStagingDisplayList;
+    mStagingDisplayList = displayList;
 }
 
 /**
@@ -97,12 +97,16 @@
             SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
 
     properties().debugOutputProperties(level);
-    int flags = DisplayListOp::kOpLogFlag_Recurse;
-    if (mDisplayListData) {
+
+    if (mDisplayList) {
+#if HWUI_NEW_OPS
+        LOG_ALWAYS_FATAL("op dumping unsupported");
+#else
         // TODO: consider printing the chunk boundaries here
-        for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
-            mDisplayListData->displayListOps[i]->output(level, flags);
+        for (auto&& op : mDisplayList->getOps()) {
+            op->output(level, DisplayListOp::kOpLogFlag_Recurse);
         }
+#endif
     }
 
     ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName());
@@ -173,8 +177,8 @@
     }
 
     pnode->clear_children();
-    if (mDisplayListData) {
-        for (auto&& child : mDisplayListData->children()) {
+    if (mDisplayList) {
+        for (auto&& child : mDisplayList->getChildren()) {
             child->renderNode->copyTo(pnode->add_children());
         }
     }
@@ -182,11 +186,11 @@
 
 int RenderNode::getDebugSize() {
     int size = sizeof(RenderNode);
-    if (mStagingDisplayListData) {
-        size += mStagingDisplayListData->getUsedSize();
+    if (mStagingDisplayList) {
+        size += mStagingDisplayList->getUsedSize();
     }
-    if (mDisplayListData && mDisplayListData != mStagingDisplayListData) {
-        size += mDisplayListData->getUsedSize();
+    if (mDisplayList && mDisplayList != mStagingDisplayList) {
+        size += mDisplayList->getUsedSize();
     }
     return size;
 }
@@ -317,10 +321,10 @@
     }
 
     bool willHaveFunctor = false;
-    if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayListData) {
-        willHaveFunctor = !mStagingDisplayListData->functors.isEmpty();
-    } else if (mDisplayListData) {
-        willHaveFunctor = !mDisplayListData->functors.isEmpty();
+    if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) {
+        willHaveFunctor = !mStagingDisplayList->getFunctors().empty();
+    } else if (mDisplayList) {
+        willHaveFunctor = !mDisplayList->getFunctors().empty();
     }
     bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
             willHaveFunctor, functorsNeedLayer);
@@ -329,7 +333,7 @@
     if (info.mode == TreeInfo::MODE_FULL) {
         pushStagingDisplayListChanges(info);
     }
-    prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData);
+    prepareSubTree(info, childFunctorsNeedLayer, mDisplayList);
     pushLayerUpdate(info);
 
     info.damageAccumulator->popTransform();
@@ -374,24 +378,24 @@
 void RenderNode::syncDisplayList() {
     // Make sure we inc first so that we don't fluctuate between 0 and 1,
     // which would thrash the layer cache
-    if (mStagingDisplayListData) {
-        for (auto&& child : mStagingDisplayListData->children()) {
+    if (mStagingDisplayList) {
+        for (auto&& child : mStagingDisplayList->getChildren()) {
             child->renderNode->incParentRefCount();
         }
     }
-    deleteDisplayListData();
-    mDisplayListData = mStagingDisplayListData;
-    mStagingDisplayListData = nullptr;
-    if (mDisplayListData) {
-        for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
-            (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
+    deleteDisplayList();
+    mDisplayList = mStagingDisplayList;
+    mStagingDisplayList = nullptr;
+    if (mDisplayList) {
+        for (size_t i = 0; i < mDisplayList->getFunctors().size(); i++) {
+            (*mDisplayList->getFunctors()[i])(DrawGlInfo::kModeSync, nullptr);
         }
     }
 }
 
 void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
-    if (mNeedsDisplayListDataSync) {
-        mNeedsDisplayListDataSync = false;
+    if (mNeedsDisplayListSync) {
+        mNeedsDisplayListSync = false;
         // Damage with the old display list first then the new one to catch any
         // changes in isRenderable or, in the future, bounds
         damageSelf(info);
@@ -400,25 +404,24 @@
     }
 }
 
-void RenderNode::deleteDisplayListData() {
-    if (mDisplayListData) {
-        for (auto&& child : mDisplayListData->children()) {
+void RenderNode::deleteDisplayList() {
+    if (mDisplayList) {
+        for (auto&& child : mDisplayList->getChildren()) {
             child->renderNode->decParentRefCount();
         }
     }
-    delete mDisplayListData;
-    mDisplayListData = nullptr;
+    delete mDisplayList;
+    mDisplayList = nullptr;
 }
 
-void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree) {
+void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree) {
     if (subtree) {
         TextureCache& cache = Caches::getInstance().textureCache;
-        info.out.hasFunctors |= subtree->functors.size();
-        for (size_t i = 0; info.prepareTextures && i < subtree->bitmapResources.size(); i++) {
-            info.prepareTextures = cache.prefetchAndMarkInUse(
-                    info.canvasContext, subtree->bitmapResources[i]);
+        info.out.hasFunctors |= subtree->getFunctors().size();
+        for (auto&& bitmapResource : subtree->getBitmapResources()) {
+            info.prepareTextures = cache.prefetchAndMarkInUse(info.canvasContext, bitmapResource);
         }
-        for (auto&& op : subtree->children()) {
+        for (auto&& op : subtree->getChildren()) {
             RenderNode* childNode = op->renderNode;
 #if HWUI_NEW_OPS
             info.damageAccumulator->pushTransform(&op->localMatrix);
@@ -440,14 +443,14 @@
         LayerRenderer::destroyLayer(mLayer);
         mLayer = nullptr;
     }
-    if (mDisplayListData) {
-        for (auto&& child : mDisplayListData->children()) {
+    if (mDisplayList) {
+        for (auto&& child : mDisplayList->getChildren()) {
             child->renderNode->destroyHardwareResources();
         }
-        if (mNeedsDisplayListDataSync) {
+        if (mNeedsDisplayListSync) {
             // Next prepare tree we are going to push a new display list, so we can
             // drop our current one now
-            deleteDisplayListData();
+            deleteDisplayList();
         }
     }
 }
@@ -629,9 +632,9 @@
 
     // TODO: create temporary DDLOp and call computeOrderingImpl on top DisplayList so that
     // transform properties are applied correctly to top level children
-    if (mDisplayListData == nullptr) return;
-    for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
-        DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+    if (mDisplayList == nullptr) return;
+    for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
+        DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
         childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
     }
 #endif
@@ -643,7 +646,7 @@
         const mat4* transformFromProjectionSurface) {
 #if !HWUI_NEW_OPS
     mProjectedNodes.clear();
-    if (mDisplayListData == nullptr || mDisplayListData->isEmpty()) return;
+    if (mDisplayList == nullptr || mDisplayList->isEmpty()) return;
 
     // TODO: should avoid this calculation in most cases
     // TODO: just calculate single matrix, down to all leaf composited elements
@@ -660,11 +663,11 @@
         opState->mSkipInOrderDraw = false;
     }
 
-    if (mDisplayListData->children().size() > 0) {
-        const bool isProjectionReceiver = mDisplayListData->projectionReceiveIndex >= 0;
+    if (mDisplayList->getChildren().size() > 0) {
+        const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0;
         bool haveAppliedPropertiesToProjection = false;
-        for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
-            DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+        for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
+            DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
             RenderNode* child = childOp->renderNode;
 
             std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
@@ -746,13 +749,13 @@
     issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
 }
 
-void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk,
+void RenderNode::buildZSortedChildList(const DisplayList::Chunk& chunk,
         std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
 #if !HWUI_NEW_OPS
     if (chunk.beginChildIndex == chunk.endChildIndex) return;
 
     for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
-        DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+        DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
         RenderNode* child = childOp->renderNode;
         float childZ = child->properties().getZ();
 
@@ -916,12 +919,17 @@
     // Transform renderer to match background we're projecting onto
     // (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
     const DisplayListOp* op =
-            (mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]);
+#if HWUI_NEW_OPS
+            nullptr;
+    LOG_ALWAYS_FATAL("unsupported");
+#else
+            (mDisplayList->getOps()[mDisplayList->projectionReceiveIndex]);
+#endif
     const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op);
     const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
     renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
 
-    // If the projection reciever has an outline, we mask projected content to it
+    // If the projection receiver has an outline, we mask projected content to it
     // (which we know, apriori, are all tessellated paths)
     renderer.setProjectionPathMask(alloc, projectionReceiverOutline);
 
@@ -953,7 +961,7 @@
  */
 template <class T>
 void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
-    if (mDisplayListData->isEmpty()) {
+    if (mDisplayList->isEmpty()) {
         DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", handler.level() * 2, "",
                 this, getName());
         return;
@@ -997,6 +1005,9 @@
         setViewProperties<T>(renderer, handler);
     }
 
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("legacy op traversal not supported");
+#else
     bool quickRejected = properties().getClipToBounds()
             && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
     if (!quickRejected) {
@@ -1008,9 +1019,9 @@
                     renderer.getSaveCount() - 1, properties().getClipToBounds());
         } else {
             const int saveCountOffset = renderer.getSaveCount() - 1;
-            const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex;
-            for (size_t chunkIndex = 0; chunkIndex < mDisplayListData->getChunks().size(); chunkIndex++) {
-                const DisplayListData::Chunk& chunk = mDisplayListData->getChunks()[chunkIndex];
+            const int projectionReceiveIndex = mDisplayList->projectionReceiveIndex;
+            for (size_t chunkIndex = 0; chunkIndex < mDisplayList->getChunks().size(); chunkIndex++) {
+                const DisplayList::Chunk& chunk = mDisplayList->getChunks()[chunkIndex];
 
                 std::vector<ZDrawRenderNodeOpPair> zTranslatedNodes;
                 buildZSortedChildList(chunk, zTranslatedNodes);
@@ -1018,9 +1029,8 @@
                 issueOperationsOf3dChildren(ChildrenSelectMode::NegativeZChildren,
                         initialTransform, zTranslatedNodes, renderer, handler);
 
-
                 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
-                    DisplayListOp *op = mDisplayListData->displayListOps[opIndex];
+                    DisplayListOp *op = mDisplayList->getOps()[opIndex];
 #if DEBUG_DISPLAY_LIST
                     op->output(handler.level() + 1);
 #endif
@@ -1037,6 +1047,7 @@
             }
         }
     }
+#endif
 
     DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (handler.level() + 1) * 2, "", restoreTo);
     handler(new (alloc) RestoreToCountOp(restoreTo),
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index ff673ba..57e41c6 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -66,12 +66,12 @@
  * Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
  *
  * Recording of canvas commands is somewhat similar to SkPicture, except the canvas-recording
- * functionality is split between DisplayListCanvas (which manages the recording), DisplayListData
+ * functionality is split between DisplayListCanvas (which manages the recording), DisplayList
  * (which holds the actual data), and DisplayList (which holds properties and performs playback onto
  * a renderer).
  *
- * Note that DisplayListData is swapped out from beneath an individual DisplayList when a view's
- * recorded stream of canvas operations is refreshed. The DisplayList (and its properties) stay
+ * Note that DisplayList is swapped out from beneath an individual RenderNode when a view's
+ * recorded stream of canvas operations is refreshed. The RenderNode (and its properties) stay
  * attached.
  */
 class RenderNode : public VirtualLightRefBase {
@@ -104,7 +104,7 @@
 
     void debugDumpLayers(const char* prefix);
 
-    ANDROID_API void setStagingDisplayList(DisplayListData* newData);
+    ANDROID_API void setStagingDisplayList(DisplayList* newData);
 
     void computeOrdering();
 
@@ -116,11 +116,11 @@
     void copyTo(proto::RenderNode* node);
 
     bool isRenderable() const {
-        return mDisplayListData && !mDisplayListData->isEmpty();
+        return mDisplayList && !mDisplayList->isEmpty();
     }
 
     bool hasProjectionReceiver() const {
-        return mDisplayListData && mDisplayListData->projectionReceiveIndex >= 0;
+        return mDisplayList && mDisplayList->projectionReceiveIndex >= 0;
     }
 
     const char* getName() const {
@@ -185,16 +185,16 @@
 
     bool nothingToDraw() const {
         const Outline& outline = properties().getOutline();
-        return mDisplayListData == nullptr
+        return mDisplayList == nullptr
                 || properties().getAlpha() <= 0
                 || (outline.getShouldClip() && outline.isEmpty())
                 || properties().getScaleX() == 0
                 || properties().getScaleY() == 0;
     }
 
-    // Only call if RenderNode has DisplayListData...
-    const DisplayListData& getDisplayListData() const {
-        return *mDisplayListData;
+    // Only call if RenderNode has DisplayList...
+    const DisplayList& getDisplayList() const {
+        return *mDisplayList;
     }
 
 private:
@@ -219,7 +219,7 @@
     template <class T>
     inline void setViewProperties(OpenGLRenderer& renderer, T& handler);
 
-    void buildZSortedChildList(const DisplayListData::Chunk& chunk,
+    void buildZSortedChildList(const DisplayList::Chunk& chunk,
             std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes);
 
     template<class T>
@@ -261,11 +261,11 @@
     void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
     void pushStagingPropertiesChanges(TreeInfo& info);
     void pushStagingDisplayListChanges(TreeInfo& info);
-    void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree);
+    void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree);
     void applyLayerPropertiesToLayer(TreeInfo& info);
     void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
     void pushLayerUpdate(TreeInfo& info);
-    void deleteDisplayListData();
+    void deleteDisplayList();
     void damageSelf(TreeInfo& info);
 
     void incParentRefCount() { mParentCount++; }
@@ -277,10 +277,10 @@
     RenderProperties mProperties;
     RenderProperties mStagingProperties;
 
-    bool mNeedsDisplayListDataSync;
-    // WARNING: Do not delete this directly, you must go through deleteDisplayListData()!
-    DisplayListData* mDisplayListData;
-    DisplayListData* mStagingDisplayListData;
+    bool mNeedsDisplayListSync;
+    // WARNING: Do not delete this directly, you must go through deleteDisplayList()!
+    DisplayList* mDisplayList;
+    DisplayList* mStagingDisplayList;
 
     friend class AnimatorManager;
     AnimatorManager mAnimatorManager;
@@ -301,7 +301,7 @@
     // When this hits 0 we are no longer in the tree, so any hardware resources
     // (specifically Layers) should be released.
     // This is *NOT* thread-safe, and should therefore only be tracking
-    // mDisplayListData, not mStagingDisplayListData.
+    // mDisplayList, not mStagingDisplayList.
     uint32_t mParentCount;
 }; // class RenderNode
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index f824cc0..abef806 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -203,8 +203,8 @@
         return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject);
     }
 
-    bool setProjectionReceiver(bool shouldRecieve) {
-        return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve);
+    bool setProjectionReceiver(bool shouldReceive) {
+        return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldReceive);
     }
 
     bool isProjectionReceiver() const {
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index aeeda96..4789b33 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -158,13 +158,12 @@
     /**
      * Returns the current clip in render target coordinates.
      */
-    const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); }
+    const Rect& getRenderTargetClip() const { return mClipArea->getClipRect(); }
 
     /*
      * Accessor functions so that the clip area can stay private
      */
     bool clipIsEmpty() const { return mClipArea->isEmpty(); }
-    const Rect& getClipRect() const { return mClipArea->getClipRect(); }
     const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
     bool clipIsSimple() const { return mClipArea->isSimple(); }
     const ClipArea& getClipArea() const { return *mClipArea; }
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index 12a3e76..0835c29 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -217,7 +217,7 @@
     }
 }
 
-static void tessellateShadows(
+void tessellateShadows(
         const Matrix4* drawTransform, const Rect* localClip,
         bool isCasterOpaque, const SkPath* casterPerimeter,
         const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
index b54666b..06e567e 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -17,16 +17,22 @@
 #ifndef ANDROID_HWUI_TESSELLATION_CACHE_H
 #define ANDROID_HWUI_TESSELLATION_CACHE_H
 
-#include <utils/LruCache.h>
-#include <utils/Mutex.h>
-
 #include "Debug.h"
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "thread/TaskProcessor.h"
 #include "utils/Macros.h"
 #include "utils/Pair.h"
 
+#include <SkPaint.h>
+
+#include <utils/LruCache.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+
 class SkBitmap;
 class SkCanvas;
-class SkPaint;
 class SkPath;
 struct SkRect;
 
@@ -185,6 +191,13 @@
 
 }; // class TessellationCache
 
+void tessellateShadows(
+        const Matrix4* drawTransform, const Rect* localClip,
+        bool isCasterOpaque, const SkPath* casterPerimeter,
+        const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
+        const Vector3& lightCenter, float lightRadius,
+        VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer);
+
 }; // namespace uirenderer
 }; // namespace android
 
diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h
index 7c3f2fd..6367dbd 100644
--- a/libs/hwui/Vector.h
+++ b/libs/hwui/Vector.h
@@ -135,8 +135,8 @@
     }
 
 
-    void dump() {
-        ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z);
+    void dump(const char* label = "Vector3") const {
+        ALOGD("%s[%.2f, %.2f, %.2f]", label, x, y, z);
     }
 };
 
diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp
index fd42adb..7a62037 100644
--- a/libs/hwui/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <benchmark/Benchmark.h>
-#include <utils/Singleton.h>
 
 #include "DisplayList.h"
 #if HWUI_NEW_OPS
@@ -24,6 +23,7 @@
 #include "DisplayListCanvas.h"
 #endif
 #include "microbench/MicroBench.h"
+#include "unit_tests/TestUtils.h"
 
 using namespace android;
 using namespace android::uirenderer;
@@ -34,24 +34,24 @@
 typedef DisplayListCanvas TestCanvas;
 #endif
 
-BENCHMARK_NO_ARG(BM_DisplayListData_alloc);
-void BM_DisplayListData_alloc::Run(int iters) {
+BENCHMARK_NO_ARG(BM_DisplayList_alloc);
+void BM_DisplayList_alloc::Run(int iters) {
     StartBenchmarkTiming();
     for (int i = 0; i < iters; ++i) {
-        auto data = new DisplayListData();
-        MicroBench::DoNotOptimize(data);
-        delete data;
+        auto displayList = new DisplayList();
+        MicroBench::DoNotOptimize(displayList);
+        delete displayList;
     }
     StopBenchmarkTiming();
 }
 
-BENCHMARK_NO_ARG(BM_DisplayListData_alloc_theoretical);
-void BM_DisplayListData_alloc_theoretical::Run(int iters) {
+BENCHMARK_NO_ARG(BM_DisplayList_alloc_theoretical);
+void BM_DisplayList_alloc_theoretical::Run(int iters) {
     StartBenchmarkTiming();
     for (int i = 0; i < iters; ++i) {
-        auto data = new char[sizeof(DisplayListData)];
-        MicroBench::DoNotOptimize(data);
-        delete[] data;
+        auto displayList = new char[sizeof(DisplayList)];
+        MicroBench::DoNotOptimize(displayList);
+        delete[] displayList;
     }
     StopBenchmarkTiming();
 }
@@ -59,13 +59,13 @@
 BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_empty);
 void BM_DisplayListCanvas_record_empty::Run(int iters) {
     TestCanvas canvas(100, 100);
-    canvas.finishRecording();
+    delete canvas.finishRecording();
 
     StartBenchmarkTiming();
     for (int i = 0; i < iters; ++i) {
         canvas.reset(100, 100);
         MicroBench::DoNotOptimize(&canvas);
-        canvas.finishRecording();
+        delete canvas.finishRecording();
     }
     StopBenchmarkTiming();
 }
@@ -73,7 +73,7 @@
 BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_saverestore);
 void BM_DisplayListCanvas_record_saverestore::Run(int iters) {
     TestCanvas canvas(100, 100);
-    canvas.finishRecording();
+    delete canvas.finishRecording();
 
     StartBenchmarkTiming();
     for (int i = 0; i < iters; ++i) {
@@ -83,7 +83,7 @@
         MicroBench::DoNotOptimize(&canvas);
         canvas.restore();
         canvas.restore();
-        canvas.finishRecording();
+        delete canvas.finishRecording();
     }
     StopBenchmarkTiming();
 }
@@ -91,14 +91,48 @@
 BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_translate);
 void BM_DisplayListCanvas_record_translate::Run(int iters) {
     TestCanvas canvas(100, 100);
-    canvas.finishRecording();
+    delete canvas.finishRecording();
 
     StartBenchmarkTiming();
     for (int i = 0; i < iters; ++i) {
         canvas.reset(100, 100);
         canvas.scale(10, 10);
         MicroBench::DoNotOptimize(&canvas);
-        canvas.finishRecording();
+        delete canvas.finishRecording();
+    }
+    StopBenchmarkTiming();
+}
+
+/**
+ * Simulate a simple view drawing a background, overlapped by an image.
+ *
+ * Note that the recording commands are intentionally not perfectly efficient, as the
+ * View system frequently produces unneeded save/restores.
+ */
+BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_simpleBitmapView);
+void BM_DisplayListCanvas_record_simpleBitmapView::Run(int iters) {
+    TestCanvas canvas(100, 100);
+    delete canvas.finishRecording();
+
+    SkPaint rectPaint;
+    SkBitmap iconBitmap = TestUtils::createSkBitmap(80, 80);
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        canvas.reset(100, 100);
+        {
+            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+            canvas.drawRect(0, 0, 100, 100, rectPaint);
+            canvas.restore();
+        }
+        {
+            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+            canvas.translate(10, 10);
+            canvas.drawBitmap(iconBitmap, 0, 0, nullptr);
+            canvas.restore();
+        }
+        MicroBench::DoNotOptimize(&canvas);
+        delete canvas.finishRecording();
     }
     StopBenchmarkTiming();
 }
diff --git a/libs/hwui/microbench/LinearAllocatorBench.cpp b/libs/hwui/microbench/LinearAllocatorBench.cpp
new file mode 100644
index 0000000..75f57cb
--- /dev/null
+++ b/libs/hwui/microbench/LinearAllocatorBench.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/Benchmark.h>
+
+#include "utils/LinearAllocator.h"
+#include "microbench/MicroBench.h"
+
+#include <vector>
+
+using namespace android;
+using namespace android::uirenderer;
+
+BENCHMARK_NO_ARG(BM_LinearStdAllocator_vectorBaseline);
+void BM_LinearStdAllocator_vectorBaseline::Run(int iters) {
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        std::vector<char> v;
+        for (int j = 0; j < 200; j++) {
+            v.push_back(j);
+        }
+        MicroBench::DoNotOptimize(&v);
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_LinearStdAllocator_vector);
+void BM_LinearStdAllocator_vector::Run(int iters) {
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        LinearAllocator la;
+        LinearStdAllocator<void*> stdAllocator(la);
+        std::vector<char, LinearStdAllocator<char> > v(stdAllocator);
+        for (int j = 0; j < 200; j++) {
+            v.push_back(j);
+        }
+        MicroBench::DoNotOptimize(&v);
+    }
+    StopBenchmarkTiming();
+}
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
new file mode 100644
index 0000000..cf96d44
--- /dev/null
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/Benchmark.h>
+
+#include "BakedOpState.h"
+#include "BakedOpRenderer.h"
+#include "OpReorderer.h"
+#include "RecordedOp.h"
+#include "RecordingCanvas.h"
+#include "unit_tests/TestUtils.h"
+#include "microbench/MicroBench.h"
+
+#include <vector>
+
+using namespace android;
+using namespace android::uirenderer;
+
+auto sReorderingDisplayList = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+    SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
+    SkPaint paint;
+
+    // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
+    // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
+    canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+    for (int i = 0; i < 30; i++) {
+        canvas.translate(0, 10);
+        canvas.drawRect(0, 0, 10, 10, paint);
+        canvas.drawBitmap(bitmap, 5, 0, nullptr);
+    }
+    canvas.restore();
+});
+
+BENCHMARK_NO_ARG(BM_OpReorderer_defer);
+void BM_OpReorderer_defer::Run(int iters) {
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        OpReorderer reorderer;
+        reorderer.defer(200, 200, *sReorderingDisplayList);
+        MicroBench::DoNotOptimize(&reorderer);
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
+void BM_OpReorderer_deferAndRender::Run(int iters) {
+    TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
+        StartBenchmarkTiming();
+        for (int i = 0; i < iters; i++) {
+            OpReorderer reorderer;
+            reorderer.defer(200, 200, *sReorderingDisplayList);
+            MicroBench::DoNotOptimize(&reorderer);
+
+            BakedOpRenderer::Info info(caches, renderState, 200, 200, true);
+            reorderer.replayBakedOps<BakedOpRenderer>(info);
+        }
+        StopBenchmarkTiming();
+    });
+}
diff --git a/libs/hwui/microbench/ShadowBench.cpp b/libs/hwui/microbench/ShadowBench.cpp
new file mode 100644
index 0000000..bd51693
--- /dev/null
+++ b/libs/hwui/microbench/ShadowBench.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/Benchmark.h>
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "VertexBuffer.h"
+#include "TessellationCache.h"
+#include "microbench/MicroBench.h"
+
+#include <SkPath.h>
+
+#include <memory>
+
+using namespace android;
+using namespace android::uirenderer;
+
+struct ShadowTestData {
+    Matrix4 drawTransform;
+    Rect localClip;
+    Matrix4 casterTransformXY;
+    Matrix4 casterTransformZ;
+    Vector3 lightCenter;
+    float lightRadius;
+};
+
+void createShadowTestData(ShadowTestData* out) {
+    static float SAMPLE_DRAW_TRANSFORM[] = {
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            0, 0, 0, 1,
+    };
+    static float SAMPLE_CASTERXY[] = {
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            32, 32, 0, 1,
+    };
+    static float SAMPLE_CASTERZ[] = {
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            32, 32, 32, 1,
+    };
+    static Rect SAMPLE_CLIP(0, 0, 1536, 2048);
+    static Vector3 SAMPLE_LIGHT_CENTER{768, -400, 1600};
+    static float SAMPLE_LIGHT_RADIUS = 1600;
+
+    out->drawTransform.load(SAMPLE_DRAW_TRANSFORM);
+    out->localClip = SAMPLE_CLIP;
+    out->casterTransformXY.load(SAMPLE_CASTERXY);
+    out->casterTransformZ.load(SAMPLE_CASTERZ);
+    out->lightCenter = SAMPLE_LIGHT_CENTER;
+    out->lightRadius = SAMPLE_LIGHT_RADIUS;
+}
+
+static inline void tessellateShadows(ShadowTestData& testData, bool opaque,
+        const SkPath& shape, VertexBuffer* ambient, VertexBuffer* spot) {
+    tessellateShadows(&testData.drawTransform, &testData.localClip,
+            opaque, &shape, &testData.casterTransformXY,
+            &testData.casterTransformZ, testData.lightCenter,
+            testData.lightRadius, *ambient, *spot);
+}
+
+BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_opaque);
+void BM_TessellateShadows_roundrect_opaque::Run(int iters) {
+    ShadowTestData shadowData;
+    createShadowTestData(&shadowData);
+    SkPath path;
+    path.reset();
+    path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5);
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        std::unique_ptr<VertexBuffer> ambient(new VertexBuffer);
+        std::unique_ptr<VertexBuffer> spot(new VertexBuffer);
+        tessellateShadows(shadowData, true, path, ambient.get(), spot.get());
+        MicroBench::DoNotOptimize(ambient.get());
+        MicroBench::DoNotOptimize(spot.get());
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_translucent);
+void BM_TessellateShadows_roundrect_translucent::Run(int iters) {
+    ShadowTestData shadowData;
+    createShadowTestData(&shadowData);
+    SkPath path;
+    path.reset();
+    path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5);
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        std::unique_ptr<VertexBuffer> ambient(new VertexBuffer);
+        std::unique_ptr<VertexBuffer> spot(new VertexBuffer);
+        tessellateShadows(shadowData, false, path, ambient.get(), spot.get());
+        MicroBench::DoNotOptimize(ambient.get());
+        MicroBench::DoNotOptimize(spot.get());
+    }
+    StopBenchmarkTiming();
+}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index e1d8abd..f571426 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -266,11 +266,11 @@
 
     Frame frame = mEglManager.beginFrame(mEglSurface);
 
-#if !HWUI_NEW_OPS
-    if (frame.width() != mCanvas->getViewportWidth()
-            || frame.height() != mCanvas->getViewportHeight()) {
+    if (frame.width() != lastFrameWidth || frame.height() != lastFrameHeight) {
         // can't rely on prior content of window if viewport size changes
         dirty.setEmpty();
+        lastFrameWidth = frame.width();
+        lastFrameHeight = frame.height();
     } else if (mHaveNewSurface || frame.bufferAge() == 0) {
         // New surface needs a full draw
         dirty.setEmpty();
@@ -316,6 +316,18 @@
     mDamageHistory.next() = screenDirty;
 
     mEglManager.damageFrame(frame, dirty);
+
+#if HWUI_NEW_OPS
+    OpReorderer reorderer;
+    reorderer.defer(dirty, frame.width(), frame.height(), mRenderNodes);
+    BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
+            frame.width(), frame.height(), mOpaque);
+    // TODO: profiler().draw(mCanvas);
+    reorderer.replayBakedOps<BakedOpRenderer>(info);
+
+    bool drew = info.didDraw;
+
+#else
     mCanvas->prepareDirty(frame.width(), frame.height(),
             dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque);
 
@@ -426,20 +438,10 @@
     profiler().draw(mCanvas);
 
     bool drew = mCanvas->finish();
-
+#endif
     // Even if we decided to cancel the frame, from the perspective of jank
     // metrics the frame was swapped at this point
     mCurrentFrameInfo->markSwapBuffers();
-#else
-    OpReorderer reorderer;
-    reorderer.defer(frame.width(), frame.height(), mRenderNodes);
-    BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
-            frame.width(), frame.height(), mOpaque);
-    reorderer.replayBakedOps<BakedOpRenderer>(&info);
-
-    bool drew = info.didDraw;
-    SkRect screenDirty = SkRect::MakeWH(frame.width(), frame.height());
-#endif
 
     if (drew) {
         if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index e0cbabd..16956e6 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -141,6 +141,9 @@
 
     void freePrefetechedLayers();
 
+    int lastFrameWidth = 0;
+    int lastFrameHeight = 0;
+
     RenderThread& mRenderThread;
     EglManager& mEglManager;
     sp<ANativeWindow> mNativeWindow;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 68ee897..cae251a9 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -32,7 +32,7 @@
 namespace uirenderer {
 
 class DeferredLayerUpdater;
-class DisplayListData;
+class DisplayList;
 class RenderNode;
 
 namespace renderthread {
@@ -48,7 +48,7 @@
 
 /*
  * This is a special Super Task. It is re-used multiple times by RenderProxy,
- * and contains state (such as layer updaters & new DisplayListDatas) that is
+ * and contains state (such as layer updaters & new DisplayLists) that is
  * tracked across many frames not just a single frame.
  * It is the sync-state task, and will kick off the post-sync draw
  */
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index c9b9637..b353ae6 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -137,6 +137,8 @@
     StringCollection extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
     EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age");
     EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
+    LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"),
+            "Missing required extension EGL_KHR_swap_buffers_with_damage");
 }
 
 bool EglManager::hasEglContext() {
@@ -322,18 +324,10 @@
     }
 #endif
 
-#ifdef EGL_KHR_swap_buffers_with_damage
-    if (CC_LIKELY(Properties::swapBuffersWithDamage)) {
-        EGLint rects[4];
-        frame.map(screenDirty, rects);
-        eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects,
-                screenDirty.isEmpty() ? 0 : 1);
-    } else {
-        eglSwapBuffers(mEglDisplay, frame.mSurface);
-    }
-#else
-    eglSwapBuffers(mEglDisplay, frame.mSurface);
-#endif
+    EGLint rects[4];
+    frame.map(screenDirty, rects);
+    eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects,
+            screenDirty.isEmpty() ? 0 : 1);
 
     EGLint err = eglGetError();
     if (CC_LIKELY(err == EGL_SUCCESS)) {
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 26aae90..15ccd6a 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -574,12 +574,7 @@
     RenderThread& thread = RenderThread::getInstance();
     void* retval;
     task->setReturnPtr(&retval);
-    Mutex mutex;
-    Condition condition;
-    SignalingRenderTask syncTask(task, &mutex, &condition);
-    AutoMutex _lock(mutex);
-    thread.queue(&syncTask);
-    condition.wait(mutex);
+    thread.queueAndWait(task);
     return retval;
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index d1b62f1..338fab6 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -38,7 +38,7 @@
 
 class DeferredLayerUpdater;
 class RenderNode;
-class DisplayListData;
+class DisplayList;
 class Layer;
 class Rect;
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 64075f1..8fcd109 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -312,6 +312,16 @@
     }
 }
 
+void RenderThread::queueAndWait(RenderTask* task) {
+    Mutex mutex;
+    Condition condition;
+    SignalingRenderTask syncTask(task, &mutex, &condition);
+
+    AutoMutex _lock(mutex);
+    queue(&syncTask);
+    condition.wait(mutex);
+}
+
 void RenderThread::queueAtFront(RenderTask* task) {
     AutoMutex _lock(mLock);
     mQueue.queueAtFront(task);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 8096099..f3444a8 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -39,6 +39,7 @@
 namespace uirenderer {
 
 class RenderState;
+class TestUtils;
 
 namespace renderthread {
 
@@ -76,6 +77,7 @@
     // RenderThread takes complete ownership of tasks that are queued
     // and will delete them after they are run
     ANDROID_API void queue(RenderTask* task);
+    ANDROID_API void queueAndWait(RenderTask* task);
     ANDROID_API void queueAtFront(RenderTask* task);
     void queueAt(RenderTask* task, nsecs_t runAtNs);
     void remove(RenderTask* task);
@@ -101,6 +103,7 @@
     friend class Singleton<RenderThread>;
     friend class DispatchFrameCallbacks;
     friend class RenderProxy;
+    friend class android::uirenderer::TestUtils;
 
     RenderThread();
     virtual ~RenderThread();
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
index b3959d1..02cd77a 100644
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -106,3 +106,31 @@
     // Checking for a double-destroy case
     EXPECT_EQ(destroyed, false);
 }
+
+TEST(LinearStdAllocator, simpleAllocate) {
+    LinearAllocator la;
+    LinearStdAllocator<void*> stdAllocator(la);
+
+    std::vector<char, LinearStdAllocator<char> > v(stdAllocator);
+    v.push_back(0);
+    char* initialLocation = &v[0];
+    v.push_back(10);
+    v.push_back(20);
+    v.push_back(30);
+
+    // expect to have allocated (since no space reserved), so [0] will have moved to
+    // slightly further down in the same LinearAllocator page
+    EXPECT_LT(initialLocation, &v[0]);
+    EXPECT_GT(initialLocation + 20, &v[0]);
+
+    // expect to have allocated again inserting 4 more entries
+    char* lastLocation = &v[0];
+    v.push_back(40);
+    v.push_back(50);
+    v.push_back(60);
+    v.push_back(70);
+
+    EXPECT_LT(lastLocation, &v[0]);
+    EXPECT_GT(lastLocation + 20, &v[0]);
+
+}
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index fcaea1e..d02f89d 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -27,62 +27,113 @@
 namespace android {
 namespace uirenderer {
 
-#define UNSUPPORTED_OP(Info, Type) \
-        static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
+/**
+ * Class that redirects static operation dispatch to virtual methods on a Client class.
+ *
+ * The client is recreated for every op (so data cannot be persisted between operations), but the
+ * virtual dispatch allows for default behaviors to be specified without enumerating each operation
+ * for every test.
+ *
+ * onXXXOp methods fail by default - tests should override ops they expect
+ * startFrame/endFrame do nothing by default - tests should override to intercept
+ */
+template<class CustomClient, class Arg>
+class TestReceiver {
+public:
+#define CLIENT_METHOD(Type) \
+    virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); }
+    class Client {
+    public:
+        virtual ~Client() {};
+        MAP_OPS(CLIENT_METHOD)
+
+        virtual void startFrame(Arg& info) {}
+        virtual void endFrame(Arg& info) {}
+    };
+
+#define DISPATCHER_METHOD(Type) \
+    static void on##Type(Arg& arg, const Type& op, const BakedOpState& state) { \
+        CustomClient client; client.on##Type(arg, op, state); \
+    }
+    MAP_OPS(DISPATCHER_METHOD)
+
+    static void startFrame(Arg& info) {
+        CustomClient client;
+        client.startFrame(info);
+    }
+
+    static void endFrame(Arg& info) {
+        CustomClient client;
+        client.endFrame(info);
+    }
+};
 
 class Info {
 public:
     int index = 0;
 };
 
-class SimpleReceiver {
+// Receiver class which will fail if it receives any ops
+class FailReceiver : public TestReceiver<FailReceiver, Info>::Client {};
+
+class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client {
 public:
-    static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
-        EXPECT_EQ(1, info->index++);
+    void startFrame(Info& info) override {
+        EXPECT_EQ(0, info.index++);
     }
-    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
-        EXPECT_EQ(0, info->index++);
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, info.index++);
     }
-    UNSUPPORTED_OP(Info, RenderNodeOp)
-    UNSUPPORTED_OP(Info, SimpleRectsOp)
-    static void startFrame(Info& info) {}
-    static void endFrame(Info& info) {}
+    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(2, info.index++);
+    }
+    void endFrame(Info& info) override {
+        EXPECT_EQ(3, info.index++);
+    }
 };
 TEST(OpReorderer, simple) {
-    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
-        SkBitmap bitmap;
-        bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
-
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
         canvas.drawRect(0, 0, 100, 200, SkPaint());
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
-
     OpReorderer reorderer;
-    reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
+    reorderer.defer(200, 200, *dl);
 
     Info info;
-    reorderer.replayBakedOps<SimpleReceiver>(&info);
+    reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info);
+    EXPECT_EQ(4, info.index); // 2 ops + start + end
+}
+
+
+TEST(OpReorderer, simpleRejection) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.restore();
+    });
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
 }
 
 
 static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingReceiver {
+class SimpleBatchingReceiver : public TestReceiver<SimpleBatchingReceiver, Info>::Client {
 public:
-    static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
-        EXPECT_TRUE(info->index++ >= SIMPLE_BATCHING_LOOPS);
+    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_TRUE(info.index++ >= SIMPLE_BATCHING_LOOPS);
     }
-    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
-        EXPECT_TRUE(info->index++ < SIMPLE_BATCHING_LOOPS);
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_TRUE(info.index++ < SIMPLE_BATCHING_LOOPS);
     }
-    UNSUPPORTED_OP(Info, RenderNodeOp)
-    UNSUPPORTED_OP(Info, SimpleRectsOp)
-    static void startFrame(Info& info) {}
-    static void endFrame(Info& info) {}
 };
 TEST(OpReorderer, simpleBatching) {
-    auto dld = TestUtils::createDLD<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
-        SkBitmap bitmap;
-        bitmap.setInfo(SkImageInfo::MakeUnknown(10, 10));
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
 
         // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
         // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
@@ -96,18 +147,17 @@
     });
 
     OpReorderer reorderer;
-    reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
+    reorderer.defer(200, 200, *dl);
 
     Info info;
-    reorderer.replayBakedOps<SimpleBatchingReceiver>(&info);
+    reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info);
     EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
 }
 
-class RenderNodeReceiver {
+class RenderNodeReceiver : public TestReceiver<RenderNodeReceiver, Info>::Client {
 public:
-    UNSUPPORTED_OP(Info, BitmapOp)
-    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
-        switch(info->index++) {
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        switch(info.index++) {
         case 0:
             EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
             EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
@@ -120,10 +170,6 @@
             FAIL();
         }
     }
-    UNSUPPORTED_OP(Info, RenderNodeOp)
-    UNSUPPORTED_OP(Info, SimpleRectsOp)
-    static void startFrame(Info& info) {}
-    static void endFrame(Info& info) {}
 };
 TEST(OpReorderer, renderNode) {
     sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
@@ -134,14 +180,14 @@
 
     RenderNode* childPtr = child.get();
     sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
-            SkPaint paint;
-            paint.setColor(SK_ColorDKGRAY);
-            canvas.drawRect(0, 0, 200, 200, paint);
+        SkPaint paint;
+        paint.setColor(SK_ColorDKGRAY);
+        canvas.drawRect(0, 0, 200, 200, paint);
 
-            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
-            canvas.translate(40, 40);
-            canvas.drawRenderNode(childPtr);
-            canvas.restore();
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(40, 40);
+        canvas.drawRenderNode(childPtr);
+        canvas.restore();
     });
 
     TestUtils::syncNodePropertiesAndDisplayList(child);
@@ -151,11 +197,135 @@
     nodes.push_back(parent.get());
 
     OpReorderer reorderer;
-    reorderer.defer(200, 200, nodes);
+    reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes);
 
     Info info;
-    reorderer.replayBakedOps<RenderNodeReceiver>(&info);
+    reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info);
 }
 
+class ClippedReceiver : public TestReceiver<ClippedReceiver, Info>::Client {
+public:
+    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(0, info.index++);
+        EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
+        EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
+        EXPECT_TRUE(state.computedState.transform.isIdentity());
+    }
+};
+TEST(OpReorderer, clipped) {
+    sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) {
+        SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
+        canvas.drawBitmap(bitmap, 0, 0, nullptr);
+    });
+    TestUtils::syncNodePropertiesAndDisplayList(node);
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(node.get());
+
+    OpReorderer reorderer;
+    reorderer.defer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
+            200, 200, nodes);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<ClippedReceiver, Info>>(info);
 }
+
+
+class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client {
+public:
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(0, info.index++);
+        EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
+        EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
+        EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
+
+        Matrix4 expectedTransform;
+        expectedTransform.loadTranslate(-10, -10, 0);
+        EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
+    }
+    void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, info.index++);
+        EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+        EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
+        EXPECT_TRUE(state.computedState.transform.isIdentity());
+    }
+};
+TEST(OpReorderer, saveLayerSimple) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
+        canvas.drawRect(10, 10, 190, 190, SkPaint());
+        canvas.restore();
+    });
+
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info);
+    EXPECT_EQ(2, info.index);
 }
+
+
+// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1
+class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client {
+public:
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        const int index = info.index++;
+        if (index == 0) {
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+        } else if (index == 1) {
+            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+        } else { FAIL(); }
+    }
+    void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
+        const int index = info.index++;
+        if (index == 2) {
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+        } else if (index == 3) {
+            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
+        } else { FAIL(); }
+    }
+};
+TEST(OpReorderer, saveLayerNested) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
+        canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
+        {
+            canvas.drawRect(0, 0, 800, 800, SkPaint());
+            canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+            {
+                canvas.drawRect(0, 0, 400, 400, SkPaint());
+            }
+            canvas.restore();
+        }
+        canvas.restore();
+    });
+
+    OpReorderer reorderer;
+    reorderer.defer(800, 800, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info);
+    EXPECT_EQ(4, info.index);
+}
+
+TEST(OpReorderer, saveLayerContentRejection) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op);
+        canvas.saveLayerAlpha(200, 200, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+
+        // draw within save layer may still be recorded, but shouldn't be drawn
+        canvas.drawRect(200, 200, 400, 400, SkPaint());
+
+        canvas.restore();
+        canvas.restore();
+    });
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, *dl);
+    Info info;
+
+    // should see no ops, even within the layer, since the layer should be rejected
+    reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index c813833..c023123 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -23,30 +23,31 @@
 namespace android {
 namespace uirenderer {
 
-static void playbackOps(const std::vector<DisplayListData::Chunk>& chunks,
-        const std::vector<RecordedOp*>& ops, std::function<void(const RecordedOp&)> opReciever) {
-    for (const DisplayListData::Chunk& chunk : chunks) {
+static void playbackOps(const DisplayList& displayList,
+        std::function<void(const RecordedOp&)> opReceiver) {
+    for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
         for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
-            opReciever(*ops[opIndex]);
+            RecordedOp* op = displayList.getOps()[opIndex];
+            opReceiver(*op);
         }
     }
 }
 
 TEST(RecordingCanvas, emptyPlayback) {
-    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
         canvas.restore();
     });
-    playbackOps(dld->getChunks(), dld->getOps(), [](const RecordedOp& op) { FAIL(); });
+    playbackOps(*dl, [](const RecordedOp& op) { FAIL(); });
 }
 
 TEST(RecordingCanvas, testSimpleRectRecord) {
-    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         canvas.drawRect(10, 20, 90, 180, SkPaint());
     });
 
     int count = 0;
-    playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
+    playbackOps(*dl, [&count](const RecordedOp& op) {
         count++;
         ASSERT_EQ(RecordedOpId::RectOp, op.opId);
         ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
@@ -56,7 +57,7 @@
 }
 
 TEST(RecordingCanvas, backgroundAndImage) {
-    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap;
         bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
         SkPaint paint;
@@ -81,7 +82,7 @@
     });
 
     int count = 0;
-    playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
+    playbackOps(*dl, [&count](const RecordedOp& op) {
         if (count == 0) {
             ASSERT_EQ(RecordedOpId::RectOp, op.opId);
             ASSERT_NE(nullptr, op.paint);
@@ -108,5 +109,123 @@
     ASSERT_EQ(2, count); // two draws observed
 }
 
+TEST(RecordingCanvas, saveLayerSimple) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(10, 20, 190, 180, SkPaint());
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        Matrix4 expectedMatrix;
+        switch(count++) {
+        case 0:
+            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
+            // TODO: add asserts
+            break;
+        case 1:
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+            EXPECT_EQ(Rect(0, 0, 180, 160), op.localClipRect);
+            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+            expectedMatrix.loadTranslate(-10, -20, 0);
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+            break;
+        case 2:
+            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+            // TODO: add asserts
+            break;
+        default:
+            FAIL();
+        }
+    });
+    EXPECT_EQ(3, count);
 }
+
+TEST(RecordingCanvas, saveLayerViewportCrop) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        // shouldn't matter, since saveLayer will clip to its bounds
+        canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
+
+        canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        if (count++ == 1) {
+            Matrix4 expectedMatrix;
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+            // recorded clip rect should be intersection of
+            // viewport and saveLayer bounds, in layer space
+            EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
+            expectedMatrix.loadTranslate(-100, -100, 0);
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+    });
+    EXPECT_EQ(3, count);
 }
+
+TEST(RecordingCanvas, saveLayerRotateUnclipped) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(100, 100);
+        canvas.rotate(45);
+        canvas.translate(-50, -50);
+
+        canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(0, 0, 100, 100, SkPaint());
+        canvas.restore();
+
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        if (count++ == 1) {
+            Matrix4 expectedMatrix;
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+            // recorded rect doesn't see rotate, since recorded relative to saveLayer bounds
+            EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+            EXPECT_EQ(Rect(0, 0, 100, 100), op.unmappedBounds);
+            expectedMatrix.loadIdentity();
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+    });
+    EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayerRotateClipped) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(100, 100);
+        canvas.rotate(45);
+        canvas.translate(-200, -200);
+
+        // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
+        canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.restore();
+
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        if (count++ == 1) {
+            Matrix4 expectedMatrix;
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+            // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
+            // the parent 200x200 viewport, but prior to rotation
+            EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), op.localClipRect);
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
+            expectedMatrix.loadIdentity();
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+    });
+    EXPECT_EQ(3, count);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 257dd28..99ecc9b 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -17,8 +17,11 @@
 #define TEST_UTILS_H
 
 #include <Matrix.h>
-#include <Snapshot.h>
+#include <Rect.h>
 #include <RenderNode.h>
+#include <renderstate/RenderState.h>
+#include <renderthread/RenderThread.h>
+#include <Snapshot.h>
 
 #include <memory>
 
@@ -28,6 +31,12 @@
 #define EXPECT_MATRIX_APPROX_EQ(a, b) \
     EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
 
+#define EXPECT_RECT_APPROX_EQ(a, b) \
+    EXPECT_TRUE(MathUtils::areEqual(a.left, b.left) \
+            && MathUtils::areEqual(a.top, b.top) \
+            && MathUtils::areEqual(a.right, b.right) \
+            && MathUtils::areEqual(a.bottom, b.bottom));
+
 class TestUtils {
 public:
     static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
@@ -46,12 +55,20 @@
         return snapshot;
     }
 
+    static SkBitmap createSkBitmap(int width, int height) {
+        SkBitmap bitmap;
+        SkImageInfo info = SkImageInfo::MakeUnknown(width, height);
+        bitmap.setInfo(info);
+        bitmap.allocPixels(info);
+        return bitmap;
+    }
+
     template<class CanvasType>
-    static std::unique_ptr<DisplayListData> createDLD(int width, int height,
+    static std::unique_ptr<DisplayList> createDisplayList(int width, int height,
             std::function<void(CanvasType& canvas)> canvasCallback) {
         CanvasType canvas(width, height);
         canvasCallback(canvas);
-        return std::unique_ptr<DisplayListData>(canvas.finishRecording());
+        return std::unique_ptr<DisplayList>(canvas.finishRecording());
     }
 
     template<class CanvasType>
@@ -72,6 +89,32 @@
         node->syncProperties();
         node->syncDisplayList();
     }
+
+    typedef std::function<void(RenderState& state, Caches& caches)> RtCallback;
+
+    class TestTask : public renderthread::RenderTask {
+    public:
+        TestTask(RtCallback rtCallback)
+                : rtCallback(rtCallback) {}
+        virtual ~TestTask() {}
+        virtual void run() override {
+            // RenderState only valid once RenderThread is running, so queried here
+            RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
+
+            renderState.onGLContextCreated();
+            rtCallback(renderState, Caches::getInstance());
+            renderState.onGLContextDestroyed();
+        };
+        RtCallback rtCallback;
+    };
+
+    /**
+     * NOTE: requires surfaceflinger to run, otherwise this method will wait indefinitely.
+     */
+    static void runOnRenderThread(RtCallback rtCallback) {
+        TestTask task(rtCallback);
+        renderthread::RenderThread::getInstance().queueAndWait(&task);
+    }
 }; // class TestUtils
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp
index 0abe88b..e6a4c03 100644
--- a/libs/hwui/utils/LinearAllocator.cpp
+++ b/libs/hwui/utils/LinearAllocator.cpp
@@ -52,8 +52,8 @@
 #define ALIGN_PTR(p) ((void*)(ALIGN((size_t)p)))
 
 #if LOG_NDEBUG
-#define ADD_ALLOCATION(size)
-#define RM_ALLOCATION(size)
+#define ADD_ALLOCATION()
+#define RM_ALLOCATION()
 #else
 #include <utils/Thread.h>
 #include <utils/Timers.h>
@@ -65,18 +65,18 @@
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
     if (now > s_nextLog) {
         s_nextLog = now + milliseconds_to_nanoseconds(10);
-        ALOGV("Total memory usage: %zu kb", s_totalAllocations / 1024);
+        ALOGV("Total pages allocated: %zu", s_totalAllocations);
     }
 }
 
-static void _addAllocation(size_t size) {
+static void _addAllocation(int count) {
     android::AutoMutex lock(s_mutex);
-    s_totalAllocations += size;
+    s_totalAllocations += count;
     _logUsageLocked();
 }
 
-#define ADD_ALLOCATION(size) _addAllocation(size);
-#define RM_ALLOCATION(size) _addAllocation(-size);
+#define ADD_ALLOCATION(size) _addAllocation(1);
+#define RM_ALLOCATION(size) _addAllocation(-1);
 #endif
 
 #define min(x,y) (((x) < (y)) ? (x) : (y))
@@ -134,7 +134,7 @@
         Page* next = p->next();
         p->~Page();
         free(p);
-        RM_ALLOCATION(mPageSize);
+        RM_ALLOCATION();
         p = next;
     }
 }
@@ -238,7 +238,7 @@
 
 LinearAllocator::Page* LinearAllocator::newPage(size_t pageSize) {
     pageSize = ALIGN(pageSize + sizeof(LinearAllocator::Page));
-    ADD_ALLOCATION(pageSize);
+    ADD_ALLOCATION();
     mTotalAllocated += pageSize;
     mPageCount++;
     void* buf = malloc(pageSize);
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index d90dd82..e1c6f6c 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -29,6 +29,8 @@
 #include <stddef.h>
 #include <type_traits>
 
+#include <vector>
+
 namespace android {
 namespace uirenderer {
 
@@ -134,6 +136,54 @@
     size_t mDedicatedPageCount;
 };
 
+template <class T>
+class LinearStdAllocator {
+public:
+    typedef T value_type; // needed to implement std::allocator
+    typedef T* pointer; // needed to implement std::allocator
+
+    LinearStdAllocator(LinearAllocator& allocator)
+            : linearAllocator(allocator) {}
+    LinearStdAllocator(const LinearStdAllocator& other)
+            : linearAllocator(other.linearAllocator) {}
+    ~LinearStdAllocator() {}
+
+    // rebind marks that allocators can be rebound to different types
+    template <class U>
+    struct rebind {
+        typedef LinearStdAllocator<U> other;
+    };
+    // enable allocators to be constructed from other templated types
+    template <class U>
+    LinearStdAllocator(const LinearStdAllocator<U>& other)
+            : linearAllocator(other.linearAllocator) {}
+
+    T* allocate(size_t num, const void* = 0) {
+        return (T*)(linearAllocator.alloc(num * sizeof(T)));
+    }
+
+    void deallocate(pointer p, size_t num) {
+        // attempt to rewind, but no guarantees
+        linearAllocator.rewindIfLastAlloc(p, num * sizeof(T));
+    }
+
+    // public so template copy constructor can access
+    LinearAllocator& linearAllocator;
+};
+
+// return that all specializations of LinearStdAllocator are interchangeable
+template <class T1, class T2>
+bool operator== (const LinearStdAllocator<T1>&, const LinearStdAllocator<T2>&) { return true; }
+template <class T1, class T2>
+bool operator!= (const LinearStdAllocator<T1>&, const LinearStdAllocator<T2>&) { return false; }
+
+template <class T>
+class LsaVector : public std::vector<T, LinearStdAllocator<T>> {
+public:
+    LsaVector(const LinearStdAllocator<T>& allocator)
+            : std::vector<T, LinearStdAllocator<T>>(allocator) {}
+};
+
 }; // namespace uirenderer
 }; // namespace android
 
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
new file mode 100644
index 0000000..84aae75
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "TestWindowContext.h"
+
+#include "AnimationContext.h"
+#include "DisplayListCanvas.h"
+#include "IContextFactory.h"
+#include "RenderNode.h"
+#include "SkTypes.h"
+#include "gui/BufferQueue.h"
+#include "gui/CpuConsumer.h"
+#include "gui/IGraphicBufferConsumer.h"
+#include "gui/IGraphicBufferProducer.h"
+#include "gui/Surface.h"
+#include "renderthread/RenderProxy.h"
+
+
+namespace {
+
+/**
+ * Helper class for setting up android::uirenderer::renderthread::RenderProxy.
+ */
+class ContextFactory : public android::uirenderer::IContextFactory {
+public:
+    android::uirenderer::AnimationContext* createAnimationContext
+        (android::uirenderer::renderthread::TimeLord& clock) override {
+        return new android::uirenderer::AnimationContext(clock);
+    }
+};
+
+} // anonymous namespace
+
+namespace android {
+namespace uirenderer {
+
+/**
+  Android strong pointers (android::sp) can't hold forward-declared classes,
+  so we have to use pointer-to-implementation here if we want to hide the
+  details from our non-framework users.
+*/
+
+class TestWindowContext::TestWindowData {
+
+public:
+
+    TestWindowData(SkISize size) : mSize(size) {
+        android::BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+        mCpuConsumer = new android::CpuConsumer(mConsumer, 1);
+        mCpuConsumer->setName(android::String8("TestWindowContext"));
+        mCpuConsumer->setDefaultBufferSize(mSize.width(), mSize.height());
+        mAndroidSurface = new android::Surface(mProducer);
+        native_window_set_buffers_dimensions(mAndroidSurface.get(),
+                                             mSize.width(), mSize.height());
+        native_window_set_buffers_format(mAndroidSurface.get(),
+                                         android::PIXEL_FORMAT_RGBA_8888);
+        native_window_set_usage(mAndroidSurface.get(),
+                                GRALLOC_USAGE_SW_READ_OFTEN |
+                                GRALLOC_USAGE_SW_WRITE_NEVER |
+                                GRALLOC_USAGE_HW_RENDER);
+        mRootNode.reset(new android::uirenderer::RenderNode());
+        mRootNode->incStrong(nullptr);
+        mRootNode->mutateStagingProperties().setLeftTopRightBottom
+            (0, 0, mSize.width(), mSize.height());
+        mRootNode->mutateStagingProperties().setClipToBounds(false);
+        mRootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC);
+        ContextFactory factory;
+        mProxy.reset
+            (new android::uirenderer::renderthread::RenderProxy(false,
+                                                                mRootNode.get(),
+                                                                &factory));
+        mProxy->loadSystemProperties();
+        mProxy->initialize(mAndroidSurface.get());
+        float lightX = mSize.width() / 2.0f;
+        android::uirenderer::Vector3 lightVector { lightX, -200.0f, 800.0f };
+        mProxy->setup(mSize.width(), mSize.height(), 800.0f,
+                             255 * 0.075f, 255 * 0.15f);
+        mProxy->setLightCenter(lightVector);
+        mCanvas.reset(new
+            android::uirenderer::DisplayListCanvas(mSize.width(),
+                                                   mSize.height()));
+    }
+
+    SkCanvas* prepareToDraw() {
+        //mCanvas->reset(mSize.width(), mSize.height());
+        mCanvas->clipRect(0, 0, mSize.width(), mSize.height(),
+                               SkRegion::Op::kReplace_Op);
+        return mCanvas->asSkCanvas();
+    }
+
+    void finishDrawing() {
+        mRootNode->setStagingDisplayList(mCanvas->finishRecording());
+        mProxy->syncAndDrawFrame();
+        // Surprisingly, calling mProxy->fence() here appears to make no difference to
+        // the timings we record.
+    }
+
+    void fence() {
+        mProxy->fence();
+    }
+
+    bool capturePixels(SkBitmap* bmp) {
+        SkImageInfo destinationConfig =
+            SkImageInfo::Make(mSize.width(), mSize.height(),
+                              kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+        bmp->allocPixels(destinationConfig);
+        sk_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED,
+                    mSize.width() * mSize.height());
+
+        android::CpuConsumer::LockedBuffer nativeBuffer;
+        android::status_t retval = mCpuConsumer->lockNextBuffer(&nativeBuffer);
+        if (retval == android::BAD_VALUE) {
+            SkDebugf("write_canvas_png() got no buffer; returning transparent");
+            // No buffer ready to read - commonly triggered by dm sending us
+            // a no-op source, or calling code that doesn't do anything on this
+            // backend.
+            bmp->eraseColor(SK_ColorTRANSPARENT);
+            return false;
+        } else if (retval) {
+            SkDebugf("Failed to lock buffer to read pixels: %d.", retval);
+            return false;
+        }
+
+        // Move the pixels into the destination SkBitmap
+
+        SK_ALWAYSBREAK(nativeBuffer.format == android::PIXEL_FORMAT_RGBA_8888 &&
+                       "Native buffer not RGBA!");
+        SkImageInfo nativeConfig =
+            SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height,
+                              kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+
+        // Android stride is in pixels, Skia stride is in bytes
+        SkBitmap nativeWrapper;
+        bool success =
+            nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4);
+        if (!success) {
+            SkDebugf("Failed to wrap HWUI buffer in a SkBitmap");
+            return false;
+        }
+
+        SK_ALWAYSBREAK(bmp->colorType() == kRGBA_8888_SkColorType &&
+                       "Destination buffer not RGBA!");
+        success =
+            nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, 0);
+        if (!success) {
+            SkDebugf("Failed to extract pixels from HWUI buffer");
+            return false;
+        }
+
+        mCpuConsumer->unlockBuffer(nativeBuffer);
+
+        return true;
+    }
+
+private:
+
+    std::unique_ptr<android::uirenderer::RenderNode> mRootNode;
+    std::unique_ptr<android::uirenderer::renderthread::RenderProxy> mProxy;
+    std::unique_ptr<android::uirenderer::DisplayListCanvas> mCanvas;
+    android::sp<android::IGraphicBufferProducer> mProducer;
+    android::sp<android::IGraphicBufferConsumer> mConsumer;
+    android::sp<android::CpuConsumer> mCpuConsumer;
+    android::sp<android::Surface> mAndroidSurface;
+    SkISize mSize;
+};
+
+
+TestWindowContext::TestWindowContext() :
+    mData (nullptr) { }
+
+void TestWindowContext::initialize(int width, int height)  {
+    mData = new TestWindowData(SkISize::Make(width, height));
+}
+
+SkCanvas* TestWindowContext::prepareToDraw() {
+    return mData ? mData->prepareToDraw() : nullptr;
+}
+
+void TestWindowContext::finishDrawing() {
+    if (mData) {
+        mData->finishDrawing();
+    }
+}
+
+void TestWindowContext::fence() {
+    if (mData) {
+        mData->fence();
+    }
+}
+
+bool TestWindowContext::capturePixels(SkBitmap* bmp) {
+    return mData ? mData->capturePixels(bmp) : false;
+}
+
+} // namespace uirenderer
+} // namespace android
+
diff --git a/libs/hwui/utils/TestWindowContext.h b/libs/hwui/utils/TestWindowContext.h
new file mode 100644
index 0000000..445a11b
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TESTWINDOWCONTEXT_H_
+#define TESTWINDOWCONTEXT_H_
+
+#include <cutils/compiler.h>
+
+class SkBitmap;
+class SkCanvas;
+
+namespace android {
+
+namespace uirenderer {
+
+/**
+  Wraps all libui/libgui classes and types that external tests depend on,
+  exposing only primitive Skia types.
+*/
+
+class ANDROID_API TestWindowContext {
+
+public:
+
+    TestWindowContext();
+
+    /// We need to know the size of the window.
+    void initialize(int width, int height);
+
+    /// Returns a canvas to draw into; NULL if not yet initialize()d.
+    SkCanvas* prepareToDraw();
+
+    /// Flushes all drawing commands to HWUI; no-op if not yet initialize()d.
+    void finishDrawing();
+
+    /// Blocks until HWUI has processed all pending drawing commands;
+    /// no-op if not yet initialize()d.
+    void fence();
+
+    /// Returns false if not yet initialize()d.
+    bool capturePixels(SkBitmap* bmp);
+
+private:
+    /// Hidden implementation.
+    class TestWindowData;
+
+    TestWindowData* mData;
+
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // TESTWINDOWCONTEXT_H_
+
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 1152737..4a1d7e7 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -78,6 +78,7 @@
     mLocked.pointerAlpha = 0.0f; // pointer is initially faded
     mLocked.pointerSprite = mSpriteController->createSprite();
     mLocked.pointerIconChanged = false;
+    mLocked.requestedPointerShape = mPolicy->getDefaultPointerIconId();
 
     mLocked.buttonState = 0;
 
@@ -231,6 +232,10 @@
 void PointerController::setPresentation(Presentation presentation) {
     AutoMutex _l(mLock);
 
+    if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) {
+        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources);
+    }
+
     if (mLocked.presentation != presentation) {
         mLocked.presentation = presentation;
         mLocked.presentationChanged = true;
@@ -391,6 +396,15 @@
     updatePointerLocked();
 }
 
+void PointerController::updatePointerShape(int iconId) {
+    AutoMutex _l(mLock);
+    if (mLocked.requestedPointerShape != iconId) {
+        mLocked.requestedPointerShape = iconId;
+        mLocked.presentationChanged = true;
+        updatePointerLocked();
+    }
+}
+
 void PointerController::setPointerIcon(const SpriteIcon& icon) {
     AutoMutex _l(mLock);
 
@@ -497,8 +511,22 @@
     }
 
     if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
-        mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
-                ? mLocked.pointerIcon : mResources.spotAnchor);
+        if (mLocked.presentation == PRESENTATION_POINTER) {
+            if (mLocked.requestedPointerShape == mPolicy->getDefaultPointerIconId()) {
+                mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+            } else {
+                std::map<int, SpriteIcon>::const_iterator iter =
+                    mLocked.additionalMouseResources.find(mLocked.requestedPointerShape);
+                if (iter != mLocked.additionalMouseResources.end()) {
+                    mLocked.pointerSprite->setIcon(iter->second);
+                } else {
+                    ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerShape);
+                    mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+                }
+            }
+        } else {
+            mLocked.pointerSprite->setIcon(mResources.spotAnchor);
+        }
         mLocked.pointerIconChanged = false;
         mLocked.presentationChanged = false;
     }
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index b9e4ce7..24a1681 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -19,6 +19,8 @@
 
 #include "SpriteController.h"
 
+#include <map>
+
 #include <ui/DisplayInfo.h>
 #include <input/Input.h>
 #include <inputflinger/PointerControllerInterface.h>
@@ -40,7 +42,6 @@
     SpriteIcon spotAnchor;
 };
 
-
 /*
  * Pointer controller policy interface.
  *
@@ -57,6 +58,8 @@
 
 public:
     virtual void loadPointerResources(PointerResources* outResources) = 0;
+    virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources) = 0;
+    virtual int32_t getDefaultPointerIconId() = 0;
 };
 
 
@@ -93,6 +96,7 @@
             const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
     virtual void clearSpots();
 
+    void updatePointerShape(int iconId);
     void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
     void setPointerIcon(const SpriteIcon& icon);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
@@ -155,6 +159,10 @@
         SpriteIcon pointerIcon;
         bool pointerIconChanged;
 
+        std::map<int, SpriteIcon> additionalMouseResources;
+
+        int32_t requestedPointerShape;
+
         int32_t buttonState;
 
         Vector<Spot*> spots;
diff --git a/opengl/java/android/opengl/GLUtils.java b/opengl/java/android/opengl/GLUtils.java
index 4d890c9..d097335 100644
--- a/opengl/java/android/opengl/GLUtils.java
+++ b/opengl/java/android/opengl/GLUtils.java
@@ -261,12 +261,6 @@
         }
     }
 
-    /**
-     * Set OpenGL Tracing level for this application.
-     * @hide
-     */
-    native public static void setTracingLevel(int level);
-
     native private static int native_getInternalFormat(Bitmap bitmap);
     native private static int native_getType(Bitmap bitmap);
     native private static int native_texImage2D(int target, int level, int internalformat,
diff --git a/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml b/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml
index 473802e..66d340c 100644
--- a/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml
@@ -21,14 +21,14 @@
     <string name="backup_confirm_text" msgid="1878021282758896593">"एउटा जोडिएको डेस्कटप कम्प्युटरमा सबै डेटाको एउटा पूर्ण जगेडाको अनुरोध गरिएको छ। के तपाईँ यो हुन दिन चाहनुहुन्छ? \n\nयदि तपाईँले जगेडाको लागि आफैँ अनुरोध गर्नु भएन भने प्रक्रियालाई अगाडि बढ्न अनुमति नदिनुहोस्।"</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"मेरो डेटा ब्याकअप गर्नुहोस्"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"जगेडा नगर्नुहोस्"</string>
-    <string name="restore_confirm_text" msgid="7499866728030461776">"एउटा जडित डेस्कटप कम्प्युटरबाट सबै डेटाको पूर्ण पुनःबहाली अनुरोध गरियो। के तपाईं यो हुन अनुमति दिनुहुन्छ?\n\nयदि तपाईं आफैं पुनःबहाली अनुरोध गर्नुहुन्न भने अपरेसनलाई अघि बढाउन अनुमति नदिनुहोस्। यसले उपकरणमा भएको कुनै पनि डेटालाई बदल्ने छ!"</string>
+    <string name="restore_confirm_text" msgid="7499866728030461776">"एउटा जडित डेस्कटप कम्प्युटरबाट सबै डेटाको पूर्ण पुनःबहाली अनुरोध गरियो। के तपाईँ यो हुन अनुमति दिनुहुन्छ?\n\nयदि तपाईँ आफैं पुनःबहाली अनुरोध गर्नुहुन्न भने अपरेसनलाई अघि बढाउन अनुमति नदिनुहोस्। यसले उपकरणमा भएको कुनै पनि डेटालाई बदल्ने छ!"</string>
     <string name="allow_restore_button_label" msgid="3081286752277127827">"मेरो डेटा पुनःबहाली गर्नुहोस्"</string>
     <string name="deny_restore_button_label" msgid="1724367334453104378">"पुन:स्थापना नगर्नुहोस्"</string>
     <string name="current_password_text" msgid="8268189555578298067">"कृपया तल तपाईंको हालको ब्याकअप पासवर्ड प्रविष्टि गर्नुहोस्:"</string>
     <string name="device_encryption_restore_text" msgid="1570864916855208992">"कृपया तल तपाईंको उपकरण एन्क्रिप्सन पासवर्ड प्रविष्टि गर्नुहोस्:"</string>
     <string name="device_encryption_backup_text" msgid="5866590762672844664">"कृपया तल तपाईंको उपकरण एन्क्रिप्सन पासवर्ड प्रविष्टि गर्नुहोस्: यो ब्याकप सँग्रह एन्क्रिप्ट गर्न पनि प्रयोग हुने छ।"</string>
     <string name="backup_enc_password_text" msgid="4981585714795233099">"ब्याकप डेटालाई encrypt गर्न पासवर्ड प्रविष्टि गर्नुहोस्, यदि यो खालि छोडिएको खण्डमा तपाईको पुरानै पासवर्ड प्रयोग हुने छ।"</string>
-    <string name="backup_enc_password_optional" msgid="1350137345907579306">"यदि तपाईं पूर्ण ब्याकअप डेटा इन्क्रिप्ट गर्न चाहनु हुन्छ भने तल पासवर्ड प्रविष्टि गर्नुहोस्।"</string>
+    <string name="backup_enc_password_optional" msgid="1350137345907579306">"यदि तपाईँ पूर्ण ब्याकअप डेटा इन्क्रिप्ट गर्न चाहनु हुन्छ भने तल पासवर्ड प्रविष्टि गर्नुहोस्।"</string>
     <string name="backup_enc_password_required" msgid="7889652203371654149">"तपाईँको उपकरण गुप्तिकरण गरिए देखि, तपाईंले आफ्नो जगेडा गुप्तिकरण गर्न आवश्यक छ। कृपया तल पासवर्ड प्रविष्ट गर्नुहोस्:"</string>
     <string name="restore_enc_password_text" msgid="6140898525580710823">"यदि पुनःबहाली डेटा इन्क्रिप्ट छ भने कृपया तल पासवर्ड प्रविष्टि गर्नुहोस्:"</string>
     <string name="toast_backup_started" msgid="550354281452756121">"जगेडा राख्न सुरु हुँदै..."</string>
diff --git a/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml b/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml
index 90e2b7e..7d7a110 100644
--- a/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml
+++ b/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml
@@ -16,10 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
-        android:state_focused="true"
-        android:color="@color/platform_blue_a200"
-        android:alpha="0.1" />
-    <item
         android:state_activated="true"
         android:color="?android:attr/colorAccent"
         android:alpha="0.1" />
diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
index d124320..231e110 100644
--- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
@@ -14,10 +14,16 @@
      limitations under the License.
 -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/item_doc_list_background">
+    android:background="@drawable/item_doc_list_background"
+    android:orientation="horizontal">
+
+    <View
+        android:id="@+id/focus_indicator"
+        android:layout_width="4dp"
+        android:layout_height="match_parent" />
 
     <LinearLayout
         android:layout_width="match_parent"
@@ -121,4 +127,4 @@
 
     </LinearLayout>
 
-</FrameLayout>
+</com.android.documentsui.ListItem>
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index c576669..085df35 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -14,10 +14,16 @@
      limitations under the License.
 -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.documentsui.DocListItem xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/item_doc_list_background">
+    android:background="@drawable/item_doc_list_background"
+    android:orientation="horizontal">
+  
+    <View
+        android:id="@+id/focus_indicator"
+        android:layout_width="4dp"
+        android:layout_height="match_parent" />
 
     <LinearLayout
         android:layout_width="match_parent"
@@ -131,4 +137,4 @@
 
     </LinearLayout>
 
-</FrameLayout>
+</com.android.documentsui.DocListItem>
diff --git a/packages/DocumentsUI/res/values-af/config.xml b/packages/DocumentsUI/res/values-af/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-af/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-am/config.xml b/packages/DocumentsUI/res/values-am/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-am/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ar/config.xml b/packages/DocumentsUI/res/values-ar/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ar/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-az-rAZ/config.xml b/packages/DocumentsUI/res/values-az-rAZ/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-az-rAZ/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-bg/config.xml b/packages/DocumentsUI/res/values-bg/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-bg/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-bn-rBD/config.xml b/packages/DocumentsUI/res/values-bn-rBD/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-bn-rBD/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ca/config.xml b/packages/DocumentsUI/res/values-ca/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ca/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-cs/config.xml b/packages/DocumentsUI/res/values-cs/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-cs/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-da/config.xml b/packages/DocumentsUI/res/values-da/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-da/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-de/config.xml b/packages/DocumentsUI/res/values-de/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-de/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-el/config.xml b/packages/DocumentsUI/res/values-el/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-el/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-en-rAU/config.xml b/packages/DocumentsUI/res/values-en-rAU/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-en-rAU/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-en-rGB/config.xml b/packages/DocumentsUI/res/values-en-rGB/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-en-rGB/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-en-rIN/config.xml b/packages/DocumentsUI/res/values-en-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-en-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-es-rUS/config.xml b/packages/DocumentsUI/res/values-es-rUS/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-es-rUS/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-es/config.xml b/packages/DocumentsUI/res/values-es/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-es/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-et-rEE/config.xml b/packages/DocumentsUI/res/values-et-rEE/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-et-rEE/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-eu-rES/config.xml b/packages/DocumentsUI/res/values-eu-rES/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-eu-rES/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-fa/config.xml b/packages/DocumentsUI/res/values-fa/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-fa/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-fi/config.xml b/packages/DocumentsUI/res/values-fi/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-fi/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-fr-rCA/config.xml b/packages/DocumentsUI/res/values-fr-rCA/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-fr-rCA/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-fr/config.xml b/packages/DocumentsUI/res/values-fr/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-fr/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-gl-rES/config.xml b/packages/DocumentsUI/res/values-gl-rES/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-gl-rES/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-gu-rIN/config.xml b/packages/DocumentsUI/res/values-gu-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-gu-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-hi/config.xml b/packages/DocumentsUI/res/values-hi/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-hi/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-hr/config.xml b/packages/DocumentsUI/res/values-hr/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-hr/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-hu/config.xml b/packages/DocumentsUI/res/values-hu/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-hu/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-hy-rAM/config.xml b/packages/DocumentsUI/res/values-hy-rAM/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-hy-rAM/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-in/config.xml b/packages/DocumentsUI/res/values-in/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-in/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-is-rIS/config.xml b/packages/DocumentsUI/res/values-is-rIS/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-is-rIS/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-it/config.xml b/packages/DocumentsUI/res/values-it/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-it/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-iw/config.xml b/packages/DocumentsUI/res/values-iw/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-iw/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ja/config.xml b/packages/DocumentsUI/res/values-ja/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ja/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ka-rGE/config.xml b/packages/DocumentsUI/res/values-ka-rGE/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ka-rGE/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-kk-rKZ/config.xml b/packages/DocumentsUI/res/values-kk-rKZ/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-kk-rKZ/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-km-rKH/config.xml b/packages/DocumentsUI/res/values-km-rKH/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-km-rKH/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-kn-rIN/config.xml b/packages/DocumentsUI/res/values-kn-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-kn-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ko/config.xml b/packages/DocumentsUI/res/values-ko/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ko/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ky-rKG/config.xml b/packages/DocumentsUI/res/values-ky-rKG/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ky-rKG/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-lo-rLA/config.xml b/packages/DocumentsUI/res/values-lo-rLA/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-lo-rLA/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-lt/config.xml b/packages/DocumentsUI/res/values-lt/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-lt/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-lv/config.xml b/packages/DocumentsUI/res/values-lv/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-lv/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-mk-rMK/config.xml b/packages/DocumentsUI/res/values-mk-rMK/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-mk-rMK/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ml-rIN/config.xml b/packages/DocumentsUI/res/values-ml-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ml-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-mn-rMN/config.xml b/packages/DocumentsUI/res/values-mn-rMN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-mn-rMN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-mr-rIN/config.xml b/packages/DocumentsUI/res/values-mr-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-mr-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ms-rMY/config.xml b/packages/DocumentsUI/res/values-ms-rMY/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ms-rMY/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-my-rMM/config.xml b/packages/DocumentsUI/res/values-my-rMM/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-my-rMM/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-nb/config.xml b/packages/DocumentsUI/res/values-nb/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-nb/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ne-rNP/config.xml b/packages/DocumentsUI/res/values-ne-rNP/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ne-rNP/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-nl/config.xml b/packages/DocumentsUI/res/values-nl/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-nl/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-pa-rIN/config.xml b/packages/DocumentsUI/res/values-pa-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-pa-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-pl/config.xml b/packages/DocumentsUI/res/values-pl/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-pl/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-pt-rBR/config.xml b/packages/DocumentsUI/res/values-pt-rBR/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-pt-rBR/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-pt-rPT/config.xml b/packages/DocumentsUI/res/values-pt-rPT/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-pt-rPT/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-pt/config.xml b/packages/DocumentsUI/res/values-pt/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-pt/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ro/config.xml b/packages/DocumentsUI/res/values-ro/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ro/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ru/config.xml b/packages/DocumentsUI/res/values-ru/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ru/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-si-rLK/config.xml b/packages/DocumentsUI/res/values-si-rLK/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-si-rLK/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-sk/config.xml b/packages/DocumentsUI/res/values-sk/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-sk/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-sl/config.xml b/packages/DocumentsUI/res/values-sl/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-sl/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-sq-rAL/config.xml b/packages/DocumentsUI/res/values-sq-rAL/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-sq-rAL/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-sr/config.xml b/packages/DocumentsUI/res/values-sr/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-sr/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-sv/config.xml b/packages/DocumentsUI/res/values-sv/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-sv/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-sw/config.xml b/packages/DocumentsUI/res/values-sw/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-sw/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ta-rIN/config.xml b/packages/DocumentsUI/res/values-ta-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ta-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-te-rIN/config.xml b/packages/DocumentsUI/res/values-te-rIN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-te-rIN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-th/config.xml b/packages/DocumentsUI/res/values-th/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-th/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-tl/config.xml b/packages/DocumentsUI/res/values-tl/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-tl/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-tr/config.xml b/packages/DocumentsUI/res/values-tr/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-tr/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-uk/config.xml b/packages/DocumentsUI/res/values-uk/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-uk/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-ur-rPK/config.xml b/packages/DocumentsUI/res/values-ur-rPK/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-ur-rPK/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-uz-rUZ/config.xml b/packages/DocumentsUI/res/values-uz-rUZ/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-uz-rUZ/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-vi/config.xml b/packages/DocumentsUI/res/values-vi/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-vi/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-zh-rCN/config.xml b/packages/DocumentsUI/res/values-zh-rCN/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-zh-rCN/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-zh-rHK/config.xml b/packages/DocumentsUI/res/values-zh-rHK/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-zh-rHK/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-zh-rTW/config.xml b/packages/DocumentsUI/res/values-zh-rTW/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-zh-rTW/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values-zu/config.xml b/packages/DocumentsUI/res/values-zu/config.xml
new file mode 100644
index 0000000..843a8aa
--- /dev/null
+++ b/packages/DocumentsUI/res/values-zu/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="trusted_quick_viewer_package" msgid="3354383993907861267"></string>
+</resources>
diff --git a/packages/DocumentsUI/res/values/config.xml b/packages/DocumentsUI/res/values/config.xml
index ed7820b..73538278 100644
--- a/packages/DocumentsUI/res/values/config.xml
+++ b/packages/DocumentsUI/res/values/config.xml
@@ -16,4 +16,6 @@
 
 <resources>
     <bool name="productivity_device">true</bool>
+    <!-- intentionally unset. Vendors should set this in an overlay. -->
+    <string name="trusted_quick_viewer_package"></string>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 9d2d171..e3b1324 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -466,21 +466,6 @@
         }
     }
 
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (DEBUG) Log.d(mTag, "onKeyUp: keycode = " + keyCode);
-        DirectoryFragment dir = DirectoryFragment.get(getFragmentManager());
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_MOVE_HOME:
-                dir.focusFirstFile();
-                return true;
-            case KeyEvent.KEYCODE_MOVE_END:
-                dir.focusLastFile();
-                return true;
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
     public void onStackPicked(DocumentStack stack) {
         try {
             // Update the restored stack to ensure we have freshest data
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 94fce59..45a8907 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -17,7 +17,6 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.Shared.DEBUG;
-import static com.android.documentsui.Shared.TAG;
 import static com.android.documentsui.State.ACTION_BROWSE;
 import static com.android.documentsui.State.ACTION_CREATE;
 import static com.android.documentsui.State.ACTION_MANAGE;
@@ -66,8 +65,10 @@
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.LayoutManager;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
 import android.support.v7.widget.RecyclerView.RecyclerListener;
 import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.support.v7.widget.SimpleItemAnimator;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.text.format.Formatter;
@@ -79,12 +80,12 @@
 import android.view.ActionMode;
 import android.view.DragEvent;
 import android.view.GestureDetector;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.widget.ImageView;
@@ -97,10 +98,12 @@
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
+import com.android.internal.annotations.GuardedBy;
 
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -109,6 +112,8 @@
  */
 public class DirectoryFragment extends Fragment {
 
+    public static final String TAG = "DirectoryFragment";
+
     public static final int TYPE_NORMAL = 1;
     public static final int TYPE_SEARCH = 2;
     public static final int TYPE_RECENT_OPEN = 3;
@@ -130,7 +135,9 @@
     private static final String EXTRA_IGNORE_STATE = "ignoreState";
 
     private Model mModel;
+    private MultiSelectManager mSelectionManager;
     private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
+    private ItemClickListener mItemClickListener = new ItemClickListener();
 
     private View mEmptyView;
     private RecyclerView mRecView;
@@ -205,9 +212,12 @@
         return builder.toString();
     }
 
-    public static DirectoryFragment get(FragmentManager fm) {
+    public static @Nullable DirectoryFragment get(FragmentManager fm) {
         // TODO: deal with multiple directories shown at once
-        return (DirectoryFragment) fm.findFragmentById(R.id.container_directory);
+        Fragment fragment = fm.findFragmentById(R.id.container_directory);
+        return fragment instanceof DirectoryFragment
+                ? (DirectoryFragment) fragment
+                : null;
     }
 
     @Override
@@ -232,7 +242,7 @@
         // TODO: Rather than update columns on layout changes, push this
         // code (or something like it) into GridLayoutManager.
         mRecView.addOnLayoutChangeListener(
-                new OnLayoutChangeListener() {
+                new View.OnLayoutChangeListener() {
 
                     @Override
                     public void onLayoutChange(
@@ -245,6 +255,9 @@
                     }
                 });
 
+        // TODO: Restore transition animations.  See b/24802917.
+        ((SimpleItemAnimator) mRecView.getItemAnimator()).setSupportsChangeAnimations(false);
+
         // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
         if (DEBUG_ENABLE_DND) {
             setupDragAndDropOnDirectoryView(mRecView);
@@ -265,7 +278,7 @@
         }
 
         // Clear any outstanding selection
-        mModel.clearSelection();
+        mSelectionManager.clearSelection();
     }
 
     @Override
@@ -294,18 +307,35 @@
                     }
                 };
 
+        final GestureDetector detector = new GestureDetector(this.getContext(), listener);
+        detector.setOnDoubleTapListener(listener);
+
+        mRecView.addOnItemTouchListener(
+                new OnItemTouchListener() {
+                    @Override
+                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+                        detector.onTouchEvent(e);
+                        return false;
+                    }
+
+                    @Override
+                    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
+
+                    @Override
+                    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+                });
+
         // TODO: instead of inserting the view into the constructor, extract listener-creation code
         // and set the listener on the view after the fact.  Then the view doesn't need to be passed
-        // into the selection manager which is passed into the model.
-        MultiSelectManager selMgr= new MultiSelectManager(
+        // into the selection manager.
+        mSelectionManager = new MultiSelectManager(
                 mRecView,
-                listener,
                 state.allowMultiple
                     ? MultiSelectManager.MODE_MULTIPLE
                     : MultiSelectManager.MODE_SINGLE);
-        selMgr.addCallback(new SelectionModeListener());
+        mSelectionManager.addCallback(new SelectionModeListener());
 
-        mModel = new Model(context, selMgr, mAdapter);
+        mModel = new Model(context, mAdapter);
         mModel.addUpdateListener(mModelUpdateListener);
 
         mType = getArguments().getInt(EXTRA_TYPE);
@@ -427,7 +457,10 @@
     }
 
     private boolean onSingleTapUp(MotionEvent e) {
-        if (Events.isTouchEvent(e) && mModel.getSelection().isEmpty()) {
+        // Only respond to touch events.  Single-click mouse events are selection events and are
+        // handled by the selection manager.  Tap events that occur while the selection manager is
+        // active are also selection events.
+        if (Events.isTouchEvent(e) && !mSelectionManager.hasSelection()) {
             int position = getEventAdapterPosition(e);
             if (position != RecyclerView.NO_POSITION) {
                 return handleViewItem(position);
@@ -455,7 +488,7 @@
         if (isDocumentEnabled(docMimeType, docFlags)) {
             final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
             ((BaseActivity) getActivity()).onDocumentPicked(doc, mModel);
-            mModel.clearSelection();
+            mSelectionManager.clearSelection();
             return true;
         }
         return false;
@@ -561,7 +594,7 @@
         mRecView.setLayoutManager(layout);
         // TODO: Once b/23691541 is resolved, use a listener within MultiSelectManager instead of
         // imperatively calling this function.
-        mModel.mSelectionManager.handleLayoutChanged();
+        mSelectionManager.handleLayoutChanged();
         // setting layout manager automatically invalidates existing ViewHolders.
         mThumbSize = new Point(thumbSize, thumbSize);
     }
@@ -617,7 +650,7 @@
 
         @Override
         public void onSelectionChanged() {
-            mModel.getSelection(mSelected);
+            mSelectionManager.getSelection(mSelected);
             TypedValue color = new TypedValue();
             if (mSelected.size() > 0) {
                 if (DEBUG) Log.d(TAG, "Maybe starting action mode.");
@@ -625,8 +658,7 @@
                     if (DEBUG) Log.d(TAG, "Yeah. Starting action mode.");
                     mActionMode = getActivity().startActionMode(this);
                 }
-                getActivity().getTheme().resolveAttribute(
-                    R.attr.colorActionMode, color, true);
+                getActivity().getTheme().resolveAttribute(R.attr.colorActionMode, color, true);
                 updateActionMenu();
             } else {
                 if (DEBUG) Log.d(TAG, "Finishing action mode.");
@@ -649,16 +681,17 @@
             if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
             mActionMode = null;
             // clear selection
-            mModel.clearSelection();
+            mSelectionManager.clearSelection();
             mSelected.clear();
             mNoDeleteCount = 0;
         }
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            int size = mSelectionManager.getSelection().size();
             mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
-            mode.setTitle(TextUtils.formatSelectedCount(mModel.getSelection().size()));
-            return mModel.getSelection().size() > 0;
+            mode.setTitle(TextUtils.formatSelectedCount(size));
+            return (size > 0);
         }
 
         @Override
@@ -678,7 +711,7 @@
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
 
-            Selection selection = mModel.getSelection(new Selection());
+            Selection selection = mSelectionManager.getSelection(new Selection());
 
             final int id = item.getItemId();
             if (id == R.id.menu_open) {
@@ -802,7 +835,7 @@
         Snackbars.makeSnackbar(activity, message, Snackbar.LENGTH_LONG)
                 .setAction(
                         R.string.undo,
-                        new android.view.View.OnClickListener() {
+                        new View.OnClickListener() {
                             @Override
                             public void onClick(View view) {}
                         })
@@ -866,10 +899,16 @@
     // Provide a reference to the views for each data item
     // Complex data items may need more than one view per item, and
     // you provide access to all the views for a data item in a view holder
-    private static final class DocumentHolder extends RecyclerView.ViewHolder {
+    private static final class DocumentHolder
+            extends RecyclerView.ViewHolder
+            implements View.OnKeyListener
+    {
         // each data item is just a string in this case
         public View view;
         public String docId;  // The stable document id.
+        private ClickListener mClickListener;
+        private View.OnKeyListener mKeyListener;
+
         public DocumentHolder(View view) {
             super(view);
             this.view = view;
@@ -877,6 +916,38 @@
             // So we set it here.  Note that touch mode focus is a separate issue - see
             // View.setFocusableInTouchMode and View.isInTouchMode for more info.
             this.view.setFocusable(true);
+            this.view.setOnKeyListener(this);
+        }
+
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            // Intercept enter key-up events, and treat them as clicks.  Forward other events.
+            if (event.getAction() == KeyEvent.ACTION_UP &&
+                    keyCode == KeyEvent.KEYCODE_ENTER) {
+                if (mClickListener != null) {
+                    mClickListener.onClick(this);
+                }
+                return true;
+            } else if (mKeyListener != null) {
+                return mKeyListener.onKey(v, keyCode, event);
+            }
+            return false;
+        }
+
+        public void addClickListener(ClickListener listener) {
+            // Just handle one for now; switch to a list if necessary.
+            checkState(mClickListener == null);
+            mClickListener = listener;
+        }
+
+        public void addOnKeyListener(View.OnKeyListener listener) {
+            // Just handle one for now; switch to a list if necessary.
+            checkState(mKeyListener == null);
+            mKeyListener = listener;
+        }
+
+        interface ClickListener {
+            public void onClick(DocumentHolder doc);
         }
     }
 
@@ -917,15 +988,23 @@
         public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
             final State state = getDisplayState(DirectoryFragment.this);
             final LayoutInflater inflater = LayoutInflater.from(getContext());
+            View item = null;
             switch (state.derivedMode) {
                 case MODE_GRID:
-                    return new DocumentHolder(inflater.inflate(R.layout.item_doc_grid, parent, false));
+                    item = inflater.inflate(R.layout.item_doc_grid, parent, false);
+                    break;
                 case MODE_LIST:
-                    return new DocumentHolder(inflater.inflate(R.layout.item_doc_list, parent, false));
+                    item = inflater.inflate(R.layout.item_doc_list, parent, false);
+                    break;
                 case MODE_UNKNOWN:
                 default:
                     throw new IllegalStateException("Unsupported layout mode.");
             }
+
+            DocumentHolder holder = new DocumentHolder(item);
+            holder.addClickListener(mItemClickListener);
+            holder.addOnKeyListener(mSelectionManager);
+            return holder;
         }
 
         @Override
@@ -954,7 +1033,7 @@
 
             holder.docId = docId;
             final View itemView = holder.view;
-            itemView.setActivated(mModel.isSelected(position));
+            itemView.setActivated(isSelected(position));
 
             final View line1 = itemView.findViewById(R.id.line1);
             final View line2 = itemView.findViewById(R.id.line2);
@@ -1286,7 +1365,7 @@
     }
 
     void copySelectedToClipboard() {
-        Selection sel = mModel.getSelection(new Selection());
+        Selection sel = mSelectionManager.getSelection(new Selection());
         copySelectionToClipboard(sel);
     }
 
@@ -1335,51 +1414,12 @@
     }
 
     void selectAllFiles() {
-        boolean changed = mModel.selectAll();
+        boolean changed = mSelectionManager.setItemsSelected(0, mModel.getItemCount(), true);
         if (changed) {
             updateDisplayState();
         }
     }
 
-    /**
-     * Scrolls to the top of the file list and focuses the first file.
-     */
-    void focusFirstFile() {
-        focusFile(0);
-    }
-
-    /**
-     * Scrolls to the bottom of the file list and focuses the last file.
-     */
-    void focusLastFile() {
-        focusFile(mAdapter.getItemCount() - 1);
-    }
-
-    /**
-     * Scrolls to and then focuses on the file at the given position.
-     */
-    private void focusFile(final int pos) {
-        // Don't smooth scroll; that taxes the system unnecessarily and makes the scroll handling
-        // logic below more complicated.
-        mRecView.scrollToPosition(pos);
-
-        // If the item is already in view, focus it; otherwise, set a one-time listener to focus it
-        // when the scroll is completed.
-        RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos);
-        if (vh != null) {
-            vh.itemView.requestFocus();
-        } else {
-            mRecView.addOnScrollListener(
-                    new RecyclerView.OnScrollListener() {
-                        @Override
-                        public void onScrolled(RecyclerView view, int dx, int dy) {
-                            view.findViewHolderForAdapterPosition(pos).itemView.requestFocus();
-                            view.removeOnScrollListener(this);
-                        }
-                    });
-        }
-    }
-
     private void setupDragAndDropOnDirectoryView(View view) {
         // Listen for drops on non-directory items and empty space.
         view.setOnDragListener(mOnDragListener);
@@ -1469,9 +1509,10 @@
             return Collections.EMPTY_LIST;
         }
 
-        final List<DocumentInfo> selectedDocs = mModel.getSelectedDocuments();
+        final List<DocumentInfo> selectedDocs =
+                mModel.getDocuments(mSelectionManager.getSelection());
         if (!selectedDocs.isEmpty()) {
-            if (!mModel.isSelected(position)) {
+            if (!isSelected(position)) {
                 // There is a selection that does not include the current item, drag nothing.
                 return Collections.EMPTY_LIST;
             }
@@ -1691,61 +1732,33 @@
         public void afterActivityCreated(DirectoryFragment fragment) {}
     }
 
+    boolean isSelected(int position) {
+        return mSelectionManager.getSelection().contains(position);
+    }
+
     /**
      * The data model for the current loaded directory.
      */
     @VisibleForTesting
     public static final class Model implements DocumentContext {
-        private MultiSelectManager mSelectionManager;
         private RecyclerView.Adapter<?> mViewAdapter;
         private Context mContext;
         private int mCursorCount;
         private boolean mIsLoading;
+        @GuardedBy("mPendingDelete")
+        private Boolean mPendingDelete = false;
+        @GuardedBy("mPendingDelete")
         private SparseBooleanArray mMarkedForDeletion = new SparseBooleanArray();
         private UpdateListener mUpdateListener;
         @Nullable private Cursor mCursor;
         @Nullable private String info;
         @Nullable private String error;
 
-        Model(Context context, MultiSelectManager selectionManager,
-                RecyclerView.Adapter<?> viewAdapter) {
+        Model(Context context, RecyclerView.Adapter<?> viewAdapter) {
             mContext = context;
-            mSelectionManager = selectionManager;
             mViewAdapter = viewAdapter;
         }
 
-        /**
-         * Selects all files in the current directory.
-         * @return true if the selection state changed for any files.
-         */
-        boolean selectAll() {
-            return mSelectionManager.setItemsSelected(0, mCursorCount, true);
-        }
-
-        /**
-         * Clones the current selection into the given Selection object.
-         * @param selection
-         * @return The selection that was passed in, for convenience.
-         */
-        Selection getSelection(Selection selection) {
-            return mSelectionManager.getSelection(selection);
-        }
-
-        /**
-         * @return The current selection (the live instance, not a copy).
-         */
-        Selection getSelection() {
-            return mSelectionManager.getSelection();
-        }
-
-        boolean isSelected(int position) {
-            return mSelectionManager.getSelection().contains(position);
-        }
-
-        void clearSelection() {
-            mSelectionManager.clearSelection();
-        }
-
         void update(DirectoryResult result) {
             if (DEBUG) Log.i(TAG, "Updating model with new result set.");
 
@@ -1779,35 +1792,39 @@
         }
 
         int getItemCount() {
-            return mCursorCount - mMarkedForDeletion.size();
+            synchronized(mPendingDelete) {
+                return mCursorCount - mMarkedForDeletion.size();
+            }
         }
 
         Cursor getItem(int position) {
-            // Items marked for deletion are masked out of the UI.  To do this, for every marked
-            // item whose position is less than the requested item position, advance the requested
-            // position by 1.
-            final int originalPos = position;
-            final int size = mMarkedForDeletion.size();
-            for (int i = 0; i < size; ++i) {
-                // It'd be more concise, but less efficient, to iterate over positions while calling
-                // mMarkedForDeletion.get.  Instead, iterate over deleted entries.
-                if (mMarkedForDeletion.keyAt(i) <= position && mMarkedForDeletion.valueAt(i)) {
-                    ++position;
+            synchronized(mPendingDelete) {
+                // Items marked for deletion are masked out of the UI.  To do this, for every marked
+                // item whose position is less than the requested item position, advance the requested
+                // position by 1.
+                final int originalPos = position;
+                final int size = mMarkedForDeletion.size();
+                for (int i = 0; i < size; ++i) {
+                    // It'd be more concise, but less efficient, to iterate over positions while calling
+                    // mMarkedForDeletion.get.  Instead, iterate over deleted entries.
+                    if (mMarkedForDeletion.keyAt(i) <= position && mMarkedForDeletion.valueAt(i)) {
+                        ++position;
+                    }
                 }
-            }
 
-            if (DEBUG && position != originalPos) {
-                Log.d(TAG, "Item position adjusted for deletion.  Original: " + originalPos
-                        + "  Adjusted: " + position);
-            }
+                if (DEBUG && position != originalPos) {
+                    Log.d(TAG, "Item position adjusted for deletion.  Original: " + originalPos
+                            + "  Adjusted: " + position);
+                }
 
-            if (position >= mCursorCount) {
-                throw new IndexOutOfBoundsException("Attempt to retrieve " + position + " of " +
-                        mCursorCount + " items");
-            }
+                if (position >= mCursorCount) {
+                    throw new IndexOutOfBoundsException("Attempt to retrieve " + position + " of " +
+                            mCursorCount + " items");
+                }
 
-            mCursor.moveToPosition(position);
-            return mCursor;
+                mCursor.moveToPosition(position);
+                return mCursor;
+            }
         }
 
         private boolean isEmpty() {
@@ -1818,11 +1835,6 @@
             return mIsLoading;
         }
 
-        private List<DocumentInfo> getSelectedDocuments() {
-            Selection sel = getSelection(new Selection());
-            return getDocuments(sel);
-        }
-
         List<DocumentInfo> getDocuments(Selection items) {
             final int size = (items != null) ? items.size() : 0;
 
@@ -1845,17 +1857,19 @@
         }
 
         List<DocumentInfo> getDocumentsMarkedForDeletion() {
-            final int size = mMarkedForDeletion.size();
-            List<DocumentInfo> docs =  new ArrayList<>(size);
+            synchronized (mPendingDelete) {
+                final int size = mMarkedForDeletion.size();
+                List<DocumentInfo> docs =  new ArrayList<>(size);
 
-            for (int i = 0; i < size; ++i) {
-                final int position = mMarkedForDeletion.keyAt(i);
-                checkState(position < mCursorCount);
-                mCursor.moveToPosition(position);
-                final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(mCursor);
-                docs.add(doc);
+                for (int i = 0; i < size; ++i) {
+                    final int position = mMarkedForDeletion.keyAt(i);
+                    checkState(position < mCursorCount);
+                    mCursor.moveToPosition(position);
+                    final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(mCursor);
+                    docs.add(doc);
+                }
+                return docs;
             }
-            return docs;
         }
 
         /**
@@ -1866,17 +1880,23 @@
          * @param selected A selection representing the files to delete.
          */
         void markForDeletion(Selection selected) {
-            // Only one deletion operation at a time.
-            checkState(mMarkedForDeletion.size() == 0);
-            // There should never be more to delete than what exists.
-            checkState(mCursorCount >= selected.size());
+            synchronized (mPendingDelete) {
+                mPendingDelete = true;
+                // Only one deletion operation at a time.
+                checkState(mMarkedForDeletion.size() == 0);
+                // There should never be more to delete than what exists.
+                checkState(mCursorCount >= selected.size());
 
-            final int size = selected.size();
-            for (int i = 0; i < size; ++i) {
-                int position = selected.get(i);
-                if (DEBUG) Log.d(TAG, "Marked position " + position + " for deletion");
-                mMarkedForDeletion.append(position, true);
-                mViewAdapter.notifyItemRemoved(position);
+                int[] positions = selected.getAll();
+                Arrays.sort(positions);
+
+                // Walk backwards through the set, since we're removing positions.
+                // Otherwise, positions would change after the first modification.
+                for (int p = positions.length - 1; p >= 0; p--) {
+                    mMarkedForDeletion.append(positions[p], true);
+                    mViewAdapter.notifyItemRemoved(positions[p]);
+                    if (DEBUG) Log.d(TAG, "Scheduled " + positions[p] + " for delete.");
+                }
             }
         }
 
@@ -1885,17 +1905,24 @@
          * unmarked, and restored in the UI.  See {@link #markForDeletion(Selection)}.
          */
         void undoDeletion() {
-            // Iterate over deleted items, temporarily marking them false in the deletion list, and
-            // re-adding them to the UI.
-            final int size = mMarkedForDeletion.size();
-            for (int i = 0; i < size; ++i) {
-                final int position = mMarkedForDeletion.keyAt(i);
-                mMarkedForDeletion.put(position, false);
-                mViewAdapter.notifyItemInserted(position);
+            synchronized (mPendingDelete) {
+                // Iterate over deleted items, temporarily marking them false in the deletion list, and
+                // re-adding them to the UI.
+                final int size = mMarkedForDeletion.size();
+                for (int i = 0; i < size; ++i) {
+                    final int position = mMarkedForDeletion.keyAt(i);
+                    mMarkedForDeletion.put(position, false);
+                    mViewAdapter.notifyItemInserted(position);
+                }
+                resetDeleteData();
             }
+        }
 
-            // Then, clear the deletion list.
-            mMarkedForDeletion.clear();
+        private void resetDeleteData() {
+            synchronized (mPendingDelete) {
+                mPendingDelete = false;
+                mMarkedForDeletion.clear();
+            }
         }
 
         /**
@@ -1906,9 +1933,16 @@
          * snackbars) for errors, info, etc.
          */
         void finalizeDeletion(DeletionListener listener) {
-            final ContentResolver resolver = mContext.getContentResolver();
-            DeleteFilesTask task = new DeleteFilesTask(resolver, listener);
-            task.execute();
+            synchronized (mPendingDelete) {
+                if (mPendingDelete) {
+                    // Necessary to avoid b/25072545. Even when that's resolved, this
+                    // is a nice safe thing to day.
+                    mPendingDelete = false;
+                    final ContentResolver resolver = mContext.getContentResolver();
+                    DeleteFilesTask task = new DeleteFilesTask(resolver, listener);
+                    task.execute();
+                }
+            }
         }
 
         /**
@@ -1965,7 +1999,7 @@
                 } else {
                     if (DEBUG) Log.d(TAG, "Deletion task completed successfully.");
                 }
-                mMarkedForDeletion.clear();
+                resetDeleteData();
 
                 mListener.onCompletion();
             }
@@ -2001,6 +2035,18 @@
         }
     }
 
+    private class ItemClickListener implements DocumentHolder.ClickListener {
+        @Override
+        public void onClick(DocumentHolder doc) {
+            final int position = doc.getAdapterPosition();
+            if (mSelectionManager.hasSelection()) {
+                mSelectionManager.toggleSelection(position);
+            } else {
+                handleViewItem(position);
+            }
+        }
+    }
+
     private class ModelUpdateListener extends Model.UpdateListener {
         @Override
         public void onModelUpdate(Model model) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 6b428f5..aae5269 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -337,7 +337,7 @@
 
         if (mState.action == ACTION_CREATE) {
             final FragmentManager fm = getFragmentManager();
-            SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
+            SaveFragment.get(fm).prepareForDirectory(cwd);
         }
 
         Menus.disableHiddenItems(menu);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index c06ea0a..d4c3ba3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -38,9 +38,7 @@
      * Returns true if event was triggered by a finger or stylus touch.
      */
     static boolean isTouchEvent(MotionEvent e) {
-        return isTouchType(e.getToolType(0))
-                // Temporarily work around uiautomator's missing tool type support.
-                || isUnknownType(e.getToolType(0));
+        return isTouchType(e.getToolType(0));
     }
 
     /**
@@ -51,7 +49,7 @@
     }
 
     /**
-     * Returns true if type is finger or stylus.
+     * Returns true if event was triggered by a finger or stylus touch.
      */
     static boolean isTouchType(int toolType) {
         return toolType == MotionEvent.TOOL_TYPE_FINGER
@@ -59,13 +57,6 @@
     }
 
     /**
-     * Returns true if type is unknown.
-     */
-    static boolean isUnknownType(int toolType) {
-        return toolType == MotionEvent.TOOL_TYPE_UNKNOWN;
-    }
-
-    /**
      * Returns true if event was triggered by a finger or stylus touch.
      */
     static boolean isActionDown(MotionEvent e) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 1330b3c..3b985ec 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -323,26 +323,36 @@
     private void openDocument(DocumentInfo doc, @Nullable DocumentContext siblings) {
         Intent intent = null;
         if (siblings != null) {
-            QuickViewIntentBuilder builder =
-                    new QuickViewIntentBuilder(getPackageManager(), doc, siblings);
+            QuickViewIntentBuilder builder = new QuickViewIntentBuilder(
+                    getPackageManager(), getResources(), doc, siblings);
             intent = builder.build();
         }
 
-        // fallback to traditional VIEW action...
-        if (intent == null) {
-            intent = new Intent(Intent.ACTION_VIEW);
-            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            intent.setData(doc.derivedUri);
+        if (intent != null) {
+            // TODO: un-work around issue b/24963914. Should be fixed soon.
+            try {
+                startActivity(intent);
+                return;
+            } catch (SecurityException e) {
+                // carry on to regular view mode.
+                Log.e(TAG, "Caught security error: " + e.getLocalizedMessage());
+            }
         }
 
+        // fallback to traditional VIEW action...
+        intent = new Intent(Intent.ACTION_VIEW);
+        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.setData(doc.derivedUri);
+
         if (DEBUG && intent.getClipData() != null) {
             Log.d(TAG, "Starting intent w/ clip data: " + intent.getClipData());
         }
 
         try {
             startActivity(intent);
-        } catch (ActivityNotFoundException ex2) {
-            Snackbars.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
+        } catch (ActivityNotFoundException e) {
+            Snackbars.makeSnackbar(
+                    this, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
         }
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ListItem.java b/packages/DocumentsUI/src/com/android/documentsui/ListItem.java
new file mode 100644
index 0000000..5c40f1b
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/ListItem.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * Layout for a single item in List mode.  This class overrides the default focus listener in order
+ * to light up a focus indicator when it is focused.
+ */
+public class ListItem extends LinearLayout
+{
+    public ListItem(Context context) {
+        super(context);
+    }
+
+    public ListItem(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        View indicator = findViewById(R.id.focus_indicator);
+        if (gainFocus) {
+            TypedValue color = new TypedValue();
+            getContext().getTheme().resolveAttribute(android.R.attr.colorAccent, color, true);
+            indicator.setBackgroundColor(color.data);
+        } else {
+            indicator.setBackgroundColor(android.R.color.transparent);
+        }
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
index 858fb42..ef53d53 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
@@ -37,16 +37,13 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.GestureDetector;
-import android.view.GestureDetector.OnDoubleTapListener;
-import android.view.GestureDetector.OnGestureListener;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.documentsui.Events.InputEvent;
 import com.android.documentsui.Events.MotionInputEvent;
 
-import com.google.android.collect.Lists;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -56,7 +53,7 @@
  * Additionally it can be configured to restrict selection to a single element, @see
  * #setSelectMode.
  */
-public final class MultiSelectManager {
+public final class MultiSelectManager implements View.OnKeyListener {
 
     /** Selection mode for multiple select. **/
     public static final int MODE_MULTIPLE = 0;
@@ -72,33 +69,26 @@
     private Selection mIntermediateSelection;
 
     private Range mRanger;
+    private SelectionEnvironment mEnvironment;
+
     private final List<MultiSelectManager.Callback> mCallbacks = new ArrayList<>(1);
 
     private Adapter<?> mAdapter;
-    private ItemFinder mHelper;
     private boolean mSingleSelect;
 
     @Nullable private BandController mBandManager;
 
     /**
      * @param recyclerView
-     * @param gestureDelegate Option delegate gesture listener.
      * @param mode Selection mode
-     * @template A gestureDelegate that implements both {@link OnGestureListener}
-     *     and {@link OnDoubleTapListener}
      */
-    public <L extends OnGestureListener & OnDoubleTapListener> MultiSelectManager(
-            final RecyclerView recyclerView, L gestureDelegate, int mode) {
+    public MultiSelectManager(final RecyclerView recyclerView, int mode) {
+        this(recyclerView.getAdapter(), mode);
 
-        this(
-                recyclerView.getAdapter(),
-                new RuntimeItemFinder(recyclerView),
-                mode);
+        mEnvironment = new RuntimeSelectionEnvironment(recyclerView);
 
         if (mode == MODE_MULTIPLE) {
-            mBandManager = new BandController(
-                    mHelper,
-                    new RuntimeBandEnvironment(recyclerView));
+            mBandManager = new BandController();
         }
 
         GestureDetector.SimpleOnGestureListener listener =
@@ -115,15 +105,8 @@
                     }
                 };
 
-        CompositeOnGestureListener compositeListener =
-                new CompositeOnGestureListener(
-                        Lists.<OnGestureListener>newArrayList(listener, gestureDelegate),
-                        Lists.<OnDoubleTapListener>newArrayList(listener, gestureDelegate));
-
-        final GestureDetector detector =
-                new GestureDetector(recyclerView.getContext(), compositeListener);
-
-        detector.setOnDoubleTapListener(compositeListener);
+        final GestureDetector detector = new GestureDetector(recyclerView.getContext(), listener);
+        detector.setOnDoubleTapListener(listener);
 
         recyclerView.addOnItemTouchListener(
                 new RecyclerView.OnItemTouchListener() {
@@ -131,37 +114,15 @@
                     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                         detector.onTouchEvent(e);
 
-                        if (mBandManager == null) {
-                            return false;
+                        if (mBandManager != null) {
+                            return mBandManager.handleEvent(new MotionInputEvent(e, recyclerView));
                         }
-
-                        // b/23793622 notes the fact that we *never* receiver ACTION_DOWN
-                        // events in onTouchEvent. Where it not for this issue, we'd
-                        // push start handling down into handleInputEvent.
-                        if (mBandManager.shouldStart(e)) {
-                            // endBandSelect is handled in handleInputEvent.
-                            mBandManager.startBandSelect(
-                                    new Point((int) e.getX(), (int) e.getY()));
-                        } else if (mBandManager.isActive()
-                                && Events.isMouseEvent(e)
-                                && Events.isActionUp(e)) {
-                            // Same issue here w b/23793622. The ACTION_UP event
-                            // is only evert dispatched to onTouchEvent when
-                            // there is some associated motion. If a user taps
-                            // mouse, but doesn't move, then band select gets
-                            // started BUT not ended. Causing phantom
-                            // bands to appear when the user later clicks to start
-                            // band select.
-                            mBandManager.handleInputEvent(
-                                    new MotionInputEvent(e, recyclerView));
-                        }
-
-                        return mBandManager.isActive();
+                        return false;
                     }
 
                     @Override
                     public void onTouchEvent(RecyclerView rv, MotionEvent e) {
-                        mBandManager.handleInputEvent(
+                        mBandManager.processInputEvent(
                                 new MotionInputEvent(e, recyclerView));
                     }
                     @Override
@@ -174,13 +135,11 @@
      * @hide
      */
     @VisibleForTesting
-    MultiSelectManager(Adapter<?> adapter, ItemFinder helper, int mode) {
+    MultiSelectManager(Adapter<?> adapter, int mode) {
         checkNotNull(adapter, "'adapter' cannot be null.");
-        checkNotNull(helper, "'helper' cannot be null.");
 
         mSingleSelect = mode == MODE_SINGLE;
 
-        mHelper = helper;
         mAdapter = adapter;
 
         mAdapter.registerAdapterDataObserver(
@@ -224,6 +183,10 @@
         mCallbacks.add(callback);
     }
 
+    public boolean hasSelection() {
+        return !mSelection.isEmpty();
+    }
+
     /**
      * Returns a Selection object that provides a live view
      * on the current selection.
@@ -258,7 +221,7 @@
      */
     @VisibleForTesting
     public boolean setItemSelected(int position, boolean selected) {
-        if (mSingleSelect && !mSelection.isEmpty()) {
+        if (mSingleSelect && hasSelection()) {
             clearSelectionQuietly();
         }
         return setItemsSelected(position, 1, selected);
@@ -304,7 +267,7 @@
     private void clearSelectionQuietly() {
         mRanger = null;
 
-        if (mSelection.isEmpty()) {
+        if (!hasSelection()) {
             return;
         }
         if (mIntermediateSelection == null) {
@@ -333,7 +296,7 @@
     @VisibleForTesting
     boolean onSingleTapUp(InputEvent input) {
         if (DEBUG) Log.d(TAG, "Processing tap event.");
-        if (mSelection.isEmpty()) {
+        if (!hasSelection()) {
             // if this is a mouse click on an item, start selection mode.
             // TODO:  && input.isPrimaryButtonPressed(), but it is returning false.
             if (input.isOverItem() && input.isMouseEvent()) {
@@ -376,7 +339,7 @@
      *
      * @param position
      */
-    private void toggleSelection(int position) {
+    void toggleSelection(int position) {
         // Position may be special "no position" during certain
         // transitional phases. If so, skip handling of the event.
         if (position == RecyclerView.NO_POSITION) {
@@ -392,7 +355,7 @@
             if (!canSelect) {
                 return;
             }
-            if (mSingleSelect && !mSelection.isEmpty()) {
+            if (mSingleSelect && hasSelection()) {
                 clearSelectionQuietly();
             }
 
@@ -439,7 +402,7 @@
             if (selected) {
                 boolean canSelect = notifyBeforeItemStateChange(i, true);
                 if (canSelect) {
-                    if (mSingleSelect && !mSelection.isEmpty()) {
+                    if (mSingleSelect && hasSelection()) {
                         clearSelectionQuietly();
                     }
                     selectAndNotify(i);
@@ -638,6 +601,14 @@
             mTotalSelection = new SparseBooleanArray();
         }
 
+        @VisibleForTesting
+        public Selection(int... positions) {
+            this();
+            for (int i = 0; i < positions.length; i++) {
+                add(positions[i]);
+            }
+        }
+
         /**
          * @param position
          * @return true if the position is currently selected.
@@ -661,6 +632,18 @@
         }
 
         /**
+         * Returns an unordered array of selected positions.
+         */
+        public int[] getAll() {
+            final int size = size();
+            int[] positions = new int[size];
+            for (int i = 0; i < size; i++) {
+                positions[i] = get(i);
+            }
+            return positions;
+        }
+
+        /**
          * @return size of the selection.
          */
         public int size() {
@@ -871,36 +854,10 @@
     }
 
     /**
-     * Provides functionality for MultiSelectManager. Exists primarily to tests that are
-     * fully isolated from RecyclerView.
-     */
-    interface ItemFinder {
-        int findItemPosition(MotionEvent e);
-    }
-
-    /** ItemFinder implementation backed by good ol' RecyclerView. */
-    private static final class RuntimeItemFinder implements ItemFinder {
-
-        private final RecyclerView mView;
-
-        RuntimeItemFinder(RecyclerView view) {
-            mView = view;
-        }
-
-        @Override
-        public int findItemPosition(MotionEvent e) {
-            View view = mView.findChildViewUnder(e.getX(), e.getY());
-            return view != null
-                    ? mView.getChildAdapterPosition(view)
-                    : RecyclerView.NO_POSITION;
-        }
-    }
-
-    /**
      * Provides functionality for BandController. Exists primarily to tests that are
      * fully isolated from RecyclerView.
      */
-    interface BandEnvironment {
+    interface SelectionEnvironment {
         void showBand(Rect rect);
         void hideBand();
         void addOnScrollListener(RecyclerView.OnScrollListener listener);
@@ -913,29 +870,39 @@
         Point createAbsolutePoint(Point relativePoint);
         Rect getAbsoluteRectForChildViewAt(int index);
         int getAdapterPositionAt(int index);
+        int getAdapterPositionForChildView(View view);
         int getColumnCount();
         int getRowCount();
         int getChildCount();
         int getVisibleChildCount();
+        void focusItem(int position);
     }
 
     /** RvFacade implementation backed by good ol' RecyclerView. */
-    private static final class RuntimeBandEnvironment implements BandEnvironment {
+    private static final class RuntimeSelectionEnvironment implements SelectionEnvironment {
 
         private final RecyclerView mView;
         private final Drawable mBand;
 
         private boolean mIsOverlayShown = false;
 
-        RuntimeBandEnvironment(RecyclerView rv) {
+        RuntimeSelectionEnvironment(RecyclerView rv) {
             mView = rv;
             mBand = mView.getContext().getTheme().getDrawable(R.drawable.band_select_overlay);
         }
 
         @Override
+        public int getAdapterPositionForChildView(View view) {
+            if (view.getParent() == mView) {
+                return mView.getChildAdapterPosition(view);
+            } else {
+                return RecyclerView.NO_POSITION;
+            }
+        }
+
+        @Override
         public int getAdapterPositionAt(int index) {
-            View child = mView.getChildAt(index);
-            return mView.getChildViewHolder(child).getAdapterPosition();
+            return getAdapterPositionForChildView(mView.getChildAt(index));
         }
 
         @Override
@@ -1032,6 +999,28 @@
         public void hideBand() {
             mView.getOverlay().remove(mBand);
         }
+
+        @Override
+        public void focusItem(final int pos) {
+            // If the item is already in view, focus it; otherwise, scroll to it and focus it.
+            RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos);
+            if (vh != null) {
+                vh.itemView.requestFocus();
+            } else {
+                // Don't smooth scroll; that taxes the system unnecessarily and makes the scroll
+                // handling logic below more complicated.  See b/24865658.
+                mView.scrollToPosition(pos);
+                // Set a one-time listener to request focus when the scroll has completed.
+                mView.addOnScrollListener(
+                    new RecyclerView.OnScrollListener() {
+                        @Override
+                        public void onScrolled(RecyclerView view, int dx, int dy) {
+                            view.findViewHolderForAdapterPosition(pos).itemView.requestFocus();
+                            view.removeOnScrollListener(this);
+                        }
+                    });
+            }
+        }
     }
 
     public interface Callback {
@@ -1061,110 +1050,6 @@
     }
 
     /**
-     * A composite {@code OnGestureDetector} that allows us to delegate unhandled
-     * events to an outside party (presumably DirectoryFragment).
-     * @template A gestureDelegate that implements both {@link OnGestureListener}
-     *     and {@link OnDoubleTapListener}
-     */
-    private static final class CompositeOnGestureListener
-            implements OnGestureListener, OnDoubleTapListener {
-
-        private List<OnGestureListener> mGestureListeners;
-        private List<OnDoubleTapListener> mTapListeners;
-
-        public CompositeOnGestureListener(
-                List<OnGestureListener> gestureListeners,
-                List<OnDoubleTapListener> tapListeners) {
-            mGestureListeners = gestureListeners;
-            mTapListeners = tapListeners;
-        }
-
-        @Override
-        public boolean onDown(MotionEvent e) {
-            for (OnGestureListener l : mGestureListeners) {
-                if (l.onDown(e)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public void onShowPress(MotionEvent e) {
-            for (OnGestureListener l : mGestureListeners) {
-                l.onShowPress(e);
-            }
-        }
-
-        @Override
-        public boolean onSingleTapUp(MotionEvent e) {
-            for (OnGestureListener l : mGestureListeners) {
-                if (l.onSingleTapUp(e)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-            for (OnGestureListener l : mGestureListeners) {
-                if (l.onScroll(e1, e2, distanceX, distanceY)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public void onLongPress(MotionEvent e) {
-            for (OnGestureListener l : mGestureListeners) {
-                l.onLongPress(e);
-            }
-        }
-
-        @Override
-        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            for (OnGestureListener l : mGestureListeners) {
-                if (l.onFling(e1, e2, velocityX, velocityY)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onSingleTapConfirmed(MotionEvent e) {
-            for (OnDoubleTapListener listener : mTapListeners) {
-                if (listener.onSingleTapConfirmed(e)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onDoubleTap(MotionEvent e) {
-            for (OnDoubleTapListener listener : mTapListeners) {
-                if (listener.onDoubleTap(e)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onDoubleTapEvent(MotionEvent e) {
-            for (OnDoubleTapListener listener : mTapListeners) {
-                if (listener.onDoubleTapEvent(e)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
      * Provides mouse driven band-select support when used in conjunction with {@link RecyclerView}
      * and {@link MultiSelectManager}. This class is responsible for rendering the band select
      * overlay and selecting overlaid items via MultiSelectManager.
@@ -1174,8 +1059,6 @@
 
         private static final int NOT_SET = -1;
 
-        private final ItemFinder mItemFinder;
-        private final BandEnvironment mEnvironment;
         private final Runnable mModelBuilder;
 
         @Nullable private Rect mBounds;
@@ -1188,20 +1071,41 @@
         private long mScrollStartTime = NOT_SET;
         private final Runnable mViewScroller = new ViewScroller();
 
-        public BandController(ItemFinder finder, final BandEnvironment environment) {
-            mItemFinder = finder;
-            mEnvironment = environment;
+        public BandController() {
             mEnvironment.addOnScrollListener(this);
 
             mModelBuilder = new Runnable() {
                 @Override
                 public void run() {
-                    mModel = new GridModel(environment);
+                    mModel = new GridModel(mEnvironment);
                     mModel.addOnSelectionChangedListener(BandController.this);
                 }
             };
         }
 
+        public boolean handleEvent(MotionInputEvent e) {
+            // b/23793622 notes the fact that we *never* receive ACTION_DOWN
+            // events in onTouchEvent. Where it not for this issue, we'd
+            // push start handling down into handleInputEvent.
+            if (mBandManager.shouldStart(e)) {
+                // endBandSelect is handled in handleInputEvent.
+                mBandManager.startBandSelect(e.getOrigin());
+            } else if (mBandManager.isActive()
+                    && e.isMouseEvent()
+                    && e.isActionUp()) {
+                // Same issue here w b/23793622. The ACTION_UP event
+                // is only evert dispatched to onTouchEvent when
+                // there is some associated motion. If a user taps
+                // mouse, but doesn't move, then band select gets
+                // started BUT not ended. Causing phantom
+                // bands to appear when the user later clicks to start
+                // band select.
+                mBandManager.processInputEvent(e);
+            }
+
+            return isActive();
+        }
+
         private boolean isActive() {
             return mModel != null;
         }
@@ -1220,12 +1124,12 @@
             }
         }
 
-        boolean shouldStart(MotionEvent e) {
+        boolean shouldStart(MotionInputEvent e) {
             return !isActive()
-                    && Events.isMouseEvent(e)  // a mouse
-                    && Events.isActionDown(e)  // the initial button press
+                    && e.isMouseEvent()  // a mouse
+                    && e.isActionDown()  // the initial button press
                     && mAdapter.getItemCount() > 0
-                    && mItemFinder.findItemPosition(e) == RecyclerView.NO_ID;  // in empty space
+                    && e.getItemPosition() == RecyclerView.NO_ID;  // in empty space
         }
 
         boolean shouldStop(InputEvent input) {
@@ -1238,7 +1142,7 @@
          * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
          * @param input
          */
-        private void handleInputEvent(InputEvent input) {
+        private void processInputEvent(InputEvent input) {
             checkArgument(input.isMouseEvent());
 
             if (shouldStop(input)) {
@@ -1459,7 +1363,7 @@
         private static final int LOWER_LEFT = LOWER | LEFT;
         private static final int LOWER_RIGHT = LOWER | RIGHT;
 
-        private final BandEnvironment mHelper;
+        private final SelectionEnvironment mHelper;
         private final List<OnSelectionChangedListener> mOnSelectionChangedListeners =
                 new ArrayList<>();
 
@@ -1497,7 +1401,7 @@
         // should expand from when Shift+click is used.
         private int mPositionNearestOrigin = NOT_SET;
 
-        GridModel(BandEnvironment helper) {
+        GridModel(SelectionEnvironment helper) {
             mHelper = helper;
             mHelper.addOnScrollListener(this);
         }
@@ -2041,4 +1945,71 @@
             return true;
         }
     }
+
+    // TODO: Might have to move this to a more global level.  e.g. What should happen if the
+    // user taps a file and then presses shift-down?  Currently the RecyclerView never even sees
+    // the key event.  Perhaps install a global key handler to catch those events while in
+    // selection mode?
+    @Override
+    public boolean onKey(View view, int keyCode, KeyEvent event) {
+        // Listen for key-down events.  This allows the handler to respond appropriately when
+        // the user holds down the arrow keys for navigation.
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return false;
+        }
+
+        int target = RecyclerView.NO_POSITION;
+        if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
+            target = 0;
+        } else if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
+            target = mAdapter.getItemCount() - 1;
+        } else {
+            // Find a navigation target based on the arrow key that the user pressed.  Ignore
+            // navigation targets that aren't items in the recycler view.
+            int searchDir = -1;
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    searchDir = View.FOCUS_UP;
+                    break;
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    searchDir = View.FOCUS_DOWN;
+                    break;
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    searchDir = View.FOCUS_LEFT;
+                    break;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    searchDir = View.FOCUS_RIGHT;
+                    break;
+            }
+            if (searchDir != -1) {
+                View targetView = view.focusSearch(searchDir);
+                // TargetView can be null, for example, if the user pressed <down> at the bottom of
+                // the list.
+                if (targetView != null) {
+                    target = mEnvironment.getAdapterPositionForChildView(targetView);
+                }
+            }
+        }
+
+        if (target == RecyclerView.NO_POSITION) {
+            // If there is no valid navigation target, don't handle the keypress.
+            return false;
+        }
+
+        // Focus the new file.
+        mEnvironment.focusItem(target);
+
+        if (event.isShiftPressed()) {
+            if (!hasSelection()) {
+                // If there is no selection, start a selection when the user presses shift-arrow.
+                toggleSelection(mEnvironment.getAdapterPositionForChildView(view));
+            }
+
+            mRanger.snapSelection(target);
+            notifySelectionChanged();
+        }
+
+        return true;
+    }
+
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index 607cb95..6a424a6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -25,11 +25,13 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.support.annotation.Nullable;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.documentsui.BaseActivity.DocumentContext;
@@ -43,13 +45,20 @@
     private final DocumentInfo mDocument;
     private final DocumentContext mContext;
 
-    public ClipData mClipData;
-    public int mDocumentLocation;
-    private PackageManager mPkgManager;
+    private final PackageManager mPkgManager;
+    private final Resources mResources;
+
+    private ClipData mClipData;
+    private int mDocumentLocation;
 
     public QuickViewIntentBuilder(
-            PackageManager pkgManager, DocumentInfo doc, DocumentContext context) {
+            PackageManager pkgManager,
+            Resources resources,
+            DocumentInfo doc,
+            DocumentContext context) {
+
         mPkgManager = pkgManager;
+        mResources = resources;
         mDocument = doc;
         mContext = context;
     }
@@ -61,25 +70,37 @@
     @Nullable Intent build() {
         if (DEBUG) Log.d(TAG, "Preparing intent for doc:" + mDocument.documentId);
 
+        String trustedPkg = mResources.getString(R.string.trusted_quick_viewer_package);
+
         Intent intent = new Intent(Intent.ACTION_QUICK_VIEW);
         intent.setDataAndType(mDocument.derivedUri, mDocument.mimeType);
-
-        // Try to resolve the intent. If a matching app isn't installed, it won't resolve.
-        ComponentName handler = intent.resolveActivity(mPkgManager);
-        if (handler == null) {
-            return null;
-        }
-
-        Cursor cursor = mContext.getCursor();
-        for (int i = 0; i < cursor.getCount(); i++) {
-            onNextItem(i, cursor);
-        }
-
         intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        intent.putExtra(Intent.EXTRA_INDEX, mDocumentLocation);
-        intent.setClipData(mClipData);
 
-        return intent;
+        if (TextUtils.isEmpty(trustedPkg)) {
+            if (hasRegisteredHandler(intent)) {
+                return intent;
+            }
+        } else {
+            intent.setPackage(trustedPkg);
+            if (hasRegisteredHandler(intent)) {
+                // We have a trusted handler. Load all of the docs into the intent.
+                Cursor cursor = mContext.getCursor();
+                for (int i = 0; i < cursor.getCount(); i++) {
+                    onNextItem(i, cursor);
+                }
+                intent.putExtra(Intent.EXTRA_INDEX, mDocumentLocation);
+                intent.setClipData(mClipData);
+
+                return intent;
+            }
+        }
+
+        return null;
+    }
+
+    private boolean hasRegisteredHandler(Intent intent) {
+        // Try to resolve the intent. If a matching app isn't installed, it won't resolve.
+        return intent.resolveActivity(mPkgManager) != null;
     }
 
     private void onNextItem(int index, Cursor cursor) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index c2b64fb..4bd6ae6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -36,7 +36,7 @@
 import android.util.Log;
 
 import com.android.documentsui.model.RootInfo;
-
+import com.android.internal.annotations.GuardedBy;
 import com.google.common.util.concurrent.AbstractFuture;
 
 import libcore.io.IoUtils;
@@ -79,6 +79,7 @@
     private final RootsCache mRoots;
     private final State mState;
 
+    @GuardedBy("mTasks")
     private final HashMap<RootInfo, RecentTask> mTasks = new HashMap<>();
 
     private final int mSortOrder = State.SORT_ORDER_LAST_MODIFIED;
@@ -165,6 +166,12 @@
 
     @Override
     public DirectoryResult loadInBackground() {
+        synchronized (mTasks) {
+            return loadInBackgroundLocked();
+        }
+    }
+
+    private DirectoryResult loadInBackgroundLocked() {
         if (mFirstPassLatch == null) {
             // First time through we kick off all the recent tasks, and wait
             // around to see if everyone finishes quickly.
@@ -302,8 +309,10 @@
         // Ensure the loader is stopped
         onStopLoading();
 
-        for (RecentTask task : mTasks.values()) {
-            IoUtils.closeQuietly(task);
+        synchronized (mTasks) {
+            for (RecentTask task : mTasks.values()) {
+                IoUtils.closeQuietly(task);
+            }
         }
 
         IoUtils.closeQuietly(mResult);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
index ce98db2..f3b750a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextWatcher;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -79,6 +80,17 @@
         mDisplayName = (EditText) view.findViewById(android.R.id.title);
         mDisplayName.addTextChangedListener(mDisplayNameWatcher);
         mDisplayName.setText(getArguments().getString(EXTRA_DISPLAY_NAME));
+        mDisplayName.setOnKeyListener(
+                new View.OnKeyListener() {
+                    @Override
+                    public boolean onKey(View v, int keyCode, KeyEvent event) {
+                        if (keyCode == KeyEvent.KEYCODE_ENTER && mSave.isEnabled()) {
+                            performSave();
+                            return true;
+                        }
+                        return false;
+                    }
+                });
 
         mSave = (Button) view.findViewById(android.R.id.button1);
         mSave.setOnClickListener(mSaveListener);
@@ -113,17 +125,22 @@
     private View.OnClickListener mSaveListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            final DocumentsActivity activity = DocumentsActivity.get(SaveFragment.this);
-            if (mReplaceTarget != null) {
-                activity.onSaveRequested(mReplaceTarget);
-            } else {
-                final String mimeType = getArguments().getString(EXTRA_MIME_TYPE);
-                final String displayName = mDisplayName.getText().toString();
-                activity.onSaveRequested(mimeType, displayName);
-            }
+            performSave();
         }
+
     };
 
+    private void performSave() {
+        final DocumentsActivity activity = DocumentsActivity.get(SaveFragment.this);
+        if (mReplaceTarget != null) {
+            activity.onSaveRequested(mReplaceTarget);
+        } else {
+            final String mimeType = getArguments().getString(EXTRA_MIME_TYPE);
+            final String displayName = mDisplayName.getText().toString();
+            activity.onSaveRequested(mimeType, displayName);
+        }
+    }
+
     /**
      * Set given document as target for in-place writing if user hits save
      * without changing the filename. Can be set to {@code null} if user
@@ -139,7 +156,11 @@
         }
     }
 
-    public void setSaveEnabled(boolean enabled) {
+    public void prepareForDirectory(DocumentInfo cwd) {
+        setSaveEnabled(cwd != null && cwd.isCreateSupported());
+    }
+
+    private void setSaveEnabled(boolean enabled) {
         mSave.setEnabled(enabled);
     }
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
index 98ffb77..36d880a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
@@ -22,10 +22,8 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.provider.DocumentsContract.Document;
-import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.support.v7.widget.RecyclerView;
 import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
 import android.test.mock.MockContentResolver;
 import android.view.ViewGroup;
 
@@ -34,10 +32,6 @@
 import com.android.documentsui.model.DocumentInfo;
 
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-
 
 public class DirectoryFragmentModelTest extends AndroidTestCase {
 
@@ -64,7 +58,7 @@
         r.cursor = cursor;
 
         // Instantiate the model with a dummy view adapter and listener that (for now) do nothing.
-        model = new Model(mContext, null, new DummyAdapter());
+        model = new Model(mContext, new DummyAdapter());
         model.addUpdateListener(new DummyListener());
         model.update(r);
     }
@@ -119,15 +113,6 @@
         assertEquals("0", docs.get(0).documentId);
         assertEquals("1", docs.get(1).documentId);
         assertEquals("4", docs.get(2).documentId);
-
-        TestDeletionListener testListener = new TestDeletionListener();
-        model.finalizeDeletion(testListener);
-        testListener.waitForDone();
-
-        docs = getDocumentInfo(0, 1, 2);
-        assertEquals("0", docs.get(0).documentId);
-        assertEquals("1", docs.get(1).documentId);
-        assertEquals("2", docs.get(2).documentId);
     }
 
     // Tests that Model.getItem returns the right items after a deletion is undone.
@@ -151,20 +136,12 @@
         };
     }
 
-    private void delete(int... items) {
-        Selection sel = new Selection();
-        for (int item: items) {
-            sel.add(item);
-        }
-        model.markForDeletion(sel);
+    private void delete(int... positions) {
+        model.markForDeletion(new Selection(positions));
     }
 
-    private List<DocumentInfo> getDocumentInfo(int... items) {
-        Selection sel = new Selection();
-        for (int item: items) {
-            sel.add(item);
-        }
-        return model.getDocuments(sel);
+    private List<DocumentInfo> getDocumentInfo(int... positions) {
+        return model.getDocuments(new Selection(positions));
     }
 
     private static class DummyListener extends Model.UpdateListener {
@@ -179,20 +156,4 @@
             return null;
         }
     }
-
-    private static class TestDeletionListener extends Model.DeletionListener {
-        final CountDownLatch mSignal = new CountDownLatch(1);
-
-        @Override
-        public void onCompletion() {
-            mSignal.countDown();
-        }
-
-        public void waitForDone() {
-            try {
-                boolean timeout = mSignal.await(10, TimeUnit.SECONDS);
-                assertTrue("Timed out waiting for deletion completion", timeout);
-            } catch (InterruptedException e) {}
-        }
-    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java
index 2447469..ceb8cdc 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java
@@ -16,13 +16,9 @@
 
 package com.android.documentsui;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 import android.support.v7.widget.RecyclerView;
 import android.test.AndroidTestCase;
 import android.util.SparseBooleanArray;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -51,13 +47,11 @@
     private MultiSelectManager mManager;
     private TestAdapter mAdapter;
     private TestCallback mCallback;
-    private EventHelper mEventHelper;
 
     public void setUp() throws Exception {
         mAdapter = new TestAdapter(items);
         mCallback = new TestCallback();
-        mEventHelper = new EventHelper();
-        mManager = new MultiSelectManager(mAdapter, mEventHelper, MultiSelectManager.MODE_MULTIPLE);
+        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
         mManager.addCallback(mCallback);
     }
 
@@ -175,7 +169,7 @@
     }
 
     public void testSingleSelectMode() {
-        mManager = new MultiSelectManager(mAdapter, mEventHelper, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(20);
         tap(13);
@@ -183,7 +177,7 @@
     }
 
     public void testSingleSelectMode_ShiftTap() {
-        mManager = new MultiSelectManager(mAdapter, mEventHelper, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(13);
         shiftTap(20);
@@ -269,26 +263,13 @@
         assertEquals(selection.toString(), expected, selection.size());
     }
 
-    private static final class EventHelper implements MultiSelectManager.ItemFinder {
-
-        @Override
-        public int findItemPosition(MotionEvent e) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
     private static final class TestCallback implements MultiSelectManager.Callback {
 
         Set<Integer> ignored = new HashSet<>();
-        private int mLastChangedPosition;
-        private boolean mLastChangedSelected;
         private boolean mSelectionChanged = false;
 
         @Override
-        public void onItemStateChanged(int position, boolean selected) {
-            this.mLastChangedPosition = position;
-            this.mLastChangedSelected = selected;
-        }
+        public void onItemStateChanged(int position, boolean selected) {}
 
         @Override
         public boolean onBeforeItemStateChange(int position, boolean selected) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
index aa50b48..f6683fa 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
@@ -24,6 +24,7 @@
 import android.support.v7.widget.RecyclerView.OnScrollListener;
 import android.test.AndroidTestCase;
 import android.util.SparseBooleanArray;
+import android.view.View;
 
 import com.android.documentsui.MultiSelectManager.GridModel;
 
@@ -34,14 +35,14 @@
     private static final int VIEWPORT_HEIGHT = 500;
 
     private static GridModel model;
-    private static TestHelper helper;
+    private static TestEnvironment env;
     private static SparseBooleanArray lastSelection;
     private static int viewWidth;
 
     private static void setUp(int numChildren, int numColumns) {
-        helper = new TestHelper(numChildren, numColumns);
+        env = new TestEnvironment(numChildren, numColumns);
         viewWidth = VIEW_PADDING_PX + numColumns * (VIEW_PADDING_PX + CHILD_VIEW_EDGE_PX);
-        model = new GridModel(helper);
+        model = new GridModel(env);
         model.addOnSelectionChangedListener(
                 new GridModel.OnSelectionChangedListener() {
                     @Override
@@ -54,7 +55,7 @@
     @Override
     public void tearDown() {
         model = null;
-        helper = null;
+        env = null;
         lastSelection = null;
     }
 
@@ -176,12 +177,12 @@
     }
 
     private static void scroll(int dy) {
-        assertTrue(helper.verticalOffset + VIEWPORT_HEIGHT + dy <= helper.getTotalHeight());
-        helper.verticalOffset += dy;
+        assertTrue(env.verticalOffset + VIEWPORT_HEIGHT + dy <= env.getTotalHeight());
+        env.verticalOffset += dy;
         model.onScrolled(null, 0, dy);
     }
 
-    private static final class TestHelper implements MultiSelectManager.BandEnvironment {
+    private static final class TestEnvironment implements MultiSelectManager.SelectionEnvironment {
 
         public int horizontalOffset = 0;
         public int verticalOffset = 0;
@@ -189,7 +190,7 @@
         private final int mNumRows;
         private final int mNumChildren;
 
-        public TestHelper(int numChildren, int numColumns) {
+        public TestEnvironment(int numChildren, int numColumns) {
             mNumChildren = numChildren;
             mNumColumns = numColumns;
             mNumRows = (int) Math.ceil((double) numChildren / mNumColumns);
@@ -307,5 +308,15 @@
         public void removeCallback(Runnable r) {
             throw new UnsupportedOperationException();
         }
+
+        @Override
+        public int getAdapterPositionForChildView(View view) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void focusItem(int i) {
+            throw new UnsupportedOperationException();
+        }
     }
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
new file mode 100644
index 0000000..fe82c8d
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
@@ -0,0 +1,334 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# English (US), Workman keyboard layout.
+# Unlike the default (generic) keyboard layout, English (US) does not contain any
+# special ALT characters.
+#
+
+type OVERLAY
+
+map key 17 D
+map key 18 R
+map key 19 W
+map key 20 B
+map key 21 J
+map key 22 F
+map key 23 U
+map key 24 P
+map key 25 SEMICOLON
+map key 32 H
+map key 33 T
+map key 34 G
+map key 35 Y
+map key 36 N
+map key 37 E
+map key 38 O
+map key 39 I
+map key 46 M
+map key 47 C
+map key 48 V
+map key 49 K
+map key 50 L
+
+### ROW 1
+
+key GRAVE {
+    label:                              '`'
+    base:                               '`'
+    shift:                              '~'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '@'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '#'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '^'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '&'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+}
+
+key W {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    base:                               ';'
+    shift, capslock:                    ':'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    base:                               '['
+    shift:                              '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    base:                               ']'
+    shift:                              '}'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+}
+
+### ROW 3
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift:                              'O'
+}
+
+key I {
+    label:                              'I'
+    base:                               'i'
+    shift, capslock:                    'I'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    base:                               '\''
+    shift:                              '"'
+}
+
+### ROW 4
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              '<'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              '>'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+}
diff --git a/packages/InputDevices/res/values-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml
index d67a9fd..a36d01e 100644
--- a/packages/InputDevices/res/values-af/strings.xml
+++ b/packages/InputDevices/res/values-af/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engels (VS), internasionale styl"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engels (VS), Colemak-styl"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engels (VS), Dvorak-styl"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engels (VSA), Workman-styl"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Duits"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frans"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frans (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
index 3e84794..efabbbf 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"እንግሊዘኛ (ዩ. ኤስ.)፣ አለም አቀፍ ቅጥ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"እንግሊዘኛ (ዩ. ኤስ.)፣ የኮልማርክ ቅጥ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"እንግሊዘኛ (ዩ. ኤስ.)፣ የድቮራክ ቅጥ"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ጀርመን"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ፈረንሳይኛ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ፈረንሳይኛ (ካናዳ)"</string>
diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml
index a922a46..1d3d9f5 100644
--- a/packages/InputDevices/res/values-ar/strings.xml
+++ b/packages/InputDevices/res/values-ar/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"الإنجليزية (الولايات المتحدة)، النمط الدولي"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏الإنجليزية (الولايات المتحدة)، نمط Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏الإنجليزية (الولايات المتحدة)، نمط Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"الإنجليزية (الولايات المتحدة)، الحرفيّون"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"الألمانية"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"الفرنسية"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"الفرنسية (كندا)"</string>
diff --git a/packages/InputDevices/res/values-az-rAZ/strings.xml b/packages/InputDevices/res/values-az-rAZ/strings.xml
index d69059c..25d7c91 100644
--- a/packages/InputDevices/res/values-az-rAZ/strings.xml
+++ b/packages/InputDevices/res/values-az-rAZ/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilis (ABŞ), Beynəlxalq üslub"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilis (ABŞ), Colemak üslubu"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilis (ABŞ), Dvorak üslubu"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"İngilis dili (ABŞ), Workman üslubu"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alman"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransız"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransız dili (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml
index d68a347..e5e2c96 100644
--- a/packages/InputDevices/res/values-bg/strings.xml
+++ b/packages/InputDevices/res/values-bg/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"англ. (САЩ) – стил „Mеждународна“"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английски (САЩ) – стил „Коулмак“"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английски (САЩ) – стил „Дворак“"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"англ. (САЩ) – стил „Работническа“"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немски"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"френски"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"френски (Канада)"</string>
diff --git a/packages/InputDevices/res/values-bn-rBD/strings.xml b/packages/InputDevices/res/values-bn-rBD/strings.xml
index 1676a95..bfbf3d3 100644
--- a/packages/InputDevices/res/values-bn-rBD/strings.xml
+++ b/packages/InputDevices/res/values-bn-rBD/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ইংরেজি (US), আন্তর্জাতিক শৈলী"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ইংরেজি (US), কোলেম্যাক শৈলী"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ইংরেজি (US), ডিভোরাক শৈলী"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"জার্মান"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ফরাসী"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ফরাসী (কানাডা)"</string>
diff --git a/packages/InputDevices/res/values-ca/strings.xml b/packages/InputDevices/res/values-ca/strings.xml
index 6baa5b8..ee25a74 100644
--- a/packages/InputDevices/res/values-ca/strings.xml
+++ b/packages/InputDevices/res/values-ca/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglès (EUA), estil internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglès (EUA), estil Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglès (EUA), estil Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglès (EUA), estil Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemany"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francès"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francès (Canadà)"</string>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
index 1c502fe..614ba56 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), mezinárodní"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), styl Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), styl Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"německé"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francouzské"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francouzské (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml
index 043a5b3..228c51a 100644
--- a/packages/InputDevices/res/values-da/strings.xml
+++ b/packages/InputDevices/res/values-da/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), international stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelsk (USA), Workman-stil"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
index 40722f6..ce25623 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Englisch (USA), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Englisch (USA), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Englisch (USA), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Deutsch"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Französisch"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Französisch (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml
index 025a288..0a99dbb 100644
--- a/packages/InputDevices/res/values-el/strings.xml
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Αγγλικά (ΗΠΑ), τύπου International"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Αγγλικά (ΗΠΑ), τύπου Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Αγγλικά (ΗΠΑ), τύπου Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Γερμανικά"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Γαλλικά"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Γαλλικά (Καναδά)"</string>
diff --git a/packages/InputDevices/res/values-en-rAU/strings.xml b/packages/InputDevices/res/values-en-rAU/strings.xml
index d5797a0..01c2979 100644
--- a/packages/InputDevices/res/values-en-rAU/strings.xml
+++ b/packages/InputDevices/res/values-en-rAU/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-en-rGB/strings.xml b/packages/InputDevices/res/values-en-rGB/strings.xml
index d5797a0..01c2979 100644
--- a/packages/InputDevices/res/values-en-rGB/strings.xml
+++ b/packages/InputDevices/res/values-en-rGB/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-en-rIN/strings.xml b/packages/InputDevices/res/values-en-rIN/strings.xml
index d5797a0..01c2979 100644
--- a/packages/InputDevices/res/values-en-rIN/strings.xml
+++ b/packages/InputDevices/res/values-en-rIN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 0a9d2f3..20d677b 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE. UU.), internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE. UU.), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE. UU.), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-es/strings.xml b/packages/InputDevices/res/values-es/strings.xml
index 6e41abf..af1492a 100644
--- a/packages/InputDevices/res/values-es/strings.xml
+++ b/packages/InputDevices/res/values-es/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE.UU.), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE.UU.), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE.UU.), estilo Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EE. UU.) estilo Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-et-rEE/strings.xml b/packages/InputDevices/res/values-et-rEE/strings.xml
index 0d931ce..d03b82e 100644
--- a/packages/InputDevices/res/values-et-rEE/strings.xml
+++ b/packages/InputDevices/res/values-et-rEE/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglise (USA), rahvusvaheline stiil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglise (USA), Colemaki stiil"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglise (USA), Dvoraki stiil"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglise (USA), Workmani stiil"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Saksa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prantsuse"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prantsuse (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-eu-rES/strings.xml b/packages/InputDevices/res/values-eu-rES/strings.xml
index d4d7b6a..d18c6f8 100644
--- a/packages/InputDevices/res/values-eu-rES/strings.xml
+++ b/packages/InputDevices/res/values-eu-rES/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ingelesa (AEB), nazioarteko estiloa"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ingelesa (AEB), Colemak estiloa"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ingelesa (AEB), Dvorak estiloa"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ingelesa (AEB), Workman estiloa"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemaniarra"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frantsesa"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frantsesa (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
index e87fbad..a05d071 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگلیسی (ایالات متحده)، سبک بین‌المللی"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏انگلیسی (ایالات متحده)، سبک Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏انگلیسی (ایالات متحده)، سبک Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"آلمانی"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسوی"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسوی (کانادا)‏"</string>
diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml
index 5b39dfd..250333c 100644
--- a/packages/InputDevices/res/values-fi/strings.xml
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"englanti (Yhdysvallat), kansainvälinen"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"englanti (Yhdysvallat), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"englanti (Yhdysvallat), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"saksa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ranska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ranska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index 9973918..3842584 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml
index fa2977b..679dba9 100644
--- a/packages/InputDevices/res/values-fr/strings.xml
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-gl-rES/strings.xml b/packages/InputDevices/res/values-gl-rES/strings.xml
index d9babd5..ab76a14 100644
--- a/packages/InputDevices/res/values-gl-rES/strings.xml
+++ b/packages/InputDevices/res/values-gl-rES/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EUA), estilo Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-gu-rIN/strings.xml b/packages/InputDevices/res/values-gu-rIN/strings.xml
index 3abae49..63c3874 100644
--- a/packages/InputDevices/res/values-gu-rIN/strings.xml
+++ b/packages/InputDevices/res/values-gu-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"અંગ્રેજી (યુએસ), આંતરરાષ્ટ્રીય શૈલી"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"અંગ્રેજી (યુએસ), કોલેમેક શૈલી"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"અંગ્રેજી (યુએસ), ડ્વોરક શૈલી"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"જર્મન"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ફ્રેન્ચ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ફ્રેન્ચ (કેનેડા)"</string>
diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml
index fcd7f84..41966e4 100644
--- a/packages/InputDevices/res/values-hi/strings.xml
+++ b/packages/InputDevices/res/values-hi/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अंग्रेज़ी (यूएस), अंतर्राष्ट्रीय शैली"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अंग्रेज़ी (यूएस), कोलमैक शैली"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अंग्रेज़ी (यूएस), ड्वोरक शैली"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"अंग्रेज़ी (यूएस), वर्कमैन शैली"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ़्रांसीसी"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ़्रांसीसी (कनाडा)"</string>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
index bad973d..829f817 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleska (SAD), međunarodna"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleska (SAD), Colemakov raspored"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleska (SAD), Dvorakov raspored"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemačka"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francuska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francuska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-hu/strings.xml b/packages/InputDevices/res/values-hu/strings.xml
index 510591d..6fbc3eb 100644
--- a/packages/InputDevices/res/values-hu/strings.xml
+++ b/packages/InputDevices/res/values-hu/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angol (USA), nemzetközi stílus"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angol (USA), Colemak-stílus"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angol (USA), Dvorak-stílus"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"angol (USA), „Workman” stílus"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"német"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francia"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francia (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-hy-rAM/strings.xml b/packages/InputDevices/res/values-hy-rAM/strings.xml
index 9ffa8bb..f47aba2 100644
--- a/packages/InputDevices/res/values-hy-rAM/strings.xml
+++ b/packages/InputDevices/res/values-hy-rAM/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Անգլերեն (ԱՄՆ), միջազգային տեսակ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Անգլերեն (ԱՄՆ), Colemak տեսակ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Անգլերեն (ԱՄՆ), Dvorak տեսակ"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Գերմաներեն"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Ֆրանսերեն"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Ֆրանսերեն (Կանադա)"</string>
diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml
index fccfa67..b2cbd6e 100644
--- a/packages/InputDevices/res/values-in/strings.xml
+++ b/packages/InputDevices/res/values-in/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inggris (AS), gaya Internasional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inggris (AS), gaya Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inggris (AS), gaya Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inggris (AS), gaya Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Jerman"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prancis"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prancis (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-is-rIS/strings.xml b/packages/InputDevices/res/values-is-rIS/strings.xml
index e48fa49..35a5a17 100644
--- a/packages/InputDevices/res/values-is-rIS/strings.xml
+++ b/packages/InputDevices/res/values-is-rIS/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Enskt (Bandaríkin), alþjóðlegt"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Enskt (Bandaríkin), Colemak-stíll"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Enskt (Bandaríkin), Dvorak-stíll"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Þýskt"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml
index 83dba70..c1c7faa 100644
--- a/packages/InputDevices/res/values-it/strings.xml
+++ b/packages/InputDevices/res/values-it/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglese (USA), stile internazionale"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglese (USA), stile Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglese (USA), stile Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglese (USA), stile Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tedesco"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francese"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francese (Canada)"</string>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
index 26fe662..0c8b4e9 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"אנגלית (ארה\"ב), סגנון בינ\"ל"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏אנגלית (ארה\"ב), סגנון Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏אנגלית (ארה\"ב), סגנון Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"גרמנית"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"צרפתית"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"צרפתית (קנדה)"</string>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
index e2b154d..7fbddd7 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英語(アメリカ)、インターナショナル配列"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英語(アメリカ)、Colemak配列"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英語(アメリカ)、Dvorak配列"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ドイツ語"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"フランス語"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"フランス語(カナダ)"</string>
diff --git a/packages/InputDevices/res/values-ka-rGE/strings.xml b/packages/InputDevices/res/values-ka-rGE/strings.xml
index eff4b04..56bd3e3 100644
--- a/packages/InputDevices/res/values-ka-rGE/strings.xml
+++ b/packages/InputDevices/res/values-ka-rGE/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ინგლისური (აშშ), საერთაშორისო სტილი"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ინგლისური (აშშ), Colemak სტილი"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ინგლისური (აშშ), Dvorak სტილი"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"გერმანული"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ფრანგული"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ფრანგული (კანადა)"</string>
diff --git a/packages/InputDevices/res/values-kk-rKZ/strings.xml b/packages/InputDevices/res/values-kk-rKZ/strings.xml
index 7086bf7..df2da88 100644
--- a/packages/InputDevices/res/values-kk-rKZ/strings.xml
+++ b/packages/InputDevices/res/values-kk-rKZ/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ағылшын (АҚШ), Халықаралық стилі"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ағылшын (АҚШ), Colemak стилі"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ағылшын (АҚШ), Dvorak стилі"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Неміс"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-km-rKH/strings.xml b/packages/InputDevices/res/values-km-rKH/strings.xml
index 60a28b1..acd01ee 100644
--- a/packages/InputDevices/res/values-km-rKH/strings.xml
+++ b/packages/InputDevices/res/values-km-rKH/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម​​អន្តរជាតិ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"អង់គ្លេស (អាមេរិក) រចនាបទ Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"អាល្លឺម៉ង់"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"បារាំង"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"បារាំង (កាណាដា​)"</string>
diff --git a/packages/InputDevices/res/values-kn-rIN/strings.xml b/packages/InputDevices/res/values-kn-rIN/strings.xml
index 6b43517..f88076c 100644
--- a/packages/InputDevices/res/values-kn-rIN/strings.xml
+++ b/packages/InputDevices/res/values-kn-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ಇಂಗ್ಲಿಷ್ (US), ಅಂತರರಾಷ್ಟ್ರೀಯ ಶೈಲಿ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ಇಂಗ್ಲಿಷ್ (US), ಕೋಲ್ಮಾರ್ಕ್ ಶೈಲಿ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ಇಂಗ್ಲಿಷ್ (US), ಡಿವೊರಾಕ್ ಶೈಲಿ"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ಜರ್ಮನ್"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ಫ್ರೆಂಚ್"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ಫ್ರೆಂಚ್‌ (ಕೆನಡಾ)"</string>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index 3f563d1..d215375 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"영어(미국), 글로벌 스타일"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"영어(미국), 콜맥 스타일"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"영어(미국), 드보락 스타일"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"독일어"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"프랑스어"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"프랑스어(캐나다)"</string>
diff --git a/packages/InputDevices/res/values-ky-rKG/strings.xml b/packages/InputDevices/res/values-ky-rKG/strings.xml
index 0828222..ee9cc5a 100644
--- a/packages/InputDevices/res/values-ky-rKG/strings.xml
+++ b/packages/InputDevices/res/values-ky-rKG/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англис (АКШ), эл аралык"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англис (АКШ), Colemak стили"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англис (АКШ), Dvorak стили"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Немис"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-lo-rLA/strings.xml b/packages/InputDevices/res/values-lo-rLA/strings.xml
index fb3fe17..33c9f61 100644
--- a/packages/InputDevices/res/values-lo-rLA/strings.xml
+++ b/packages/InputDevices/res/values-lo-rLA/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ອັງກິດ (ສະຫະລັດຯ), ແບບສາກົນ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ເຢຍລະມັນ"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ຝຣັ່ງ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ຝຣັ່ງ (ຄານາດາ)"</string>
diff --git a/packages/InputDevices/res/values-lt/strings.xml b/packages/InputDevices/res/values-lt/strings.xml
index d0eb1f6..1dae850 100644
--- a/packages/InputDevices/res/values-lt/strings.xml
+++ b/packages/InputDevices/res/values-lt/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglų k. (JAV), tarptautinis stilius"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglų k. (JAV), „Colemak“ stilius"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglų k. (JAV), „Dvorak“ stilius"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglų k. (JAV), „Workman“ stilius"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Vokiečių k."</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prancūzų k."</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prancūzų k. (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-lv/strings.xml b/packages/InputDevices/res/values-lv/strings.xml
index 0608bf0..96dc53b 100644
--- a/packages/InputDevices/res/values-lv/strings.xml
+++ b/packages/InputDevices/res/values-lv/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angļu (ASV), starptautiska"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angļu (ASV), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angļu (ASV), Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Angļu (ASV), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Vācu"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franču"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franču (Kanāda)"</string>
diff --git a/packages/InputDevices/res/values-mk-rMK/strings.xml b/packages/InputDevices/res/values-mk-rMK/strings.xml
index 8662984..2fae1cf 100644
--- a/packages/InputDevices/res/values-mk-rMK/strings.xml
+++ b/packages/InputDevices/res/values-mk-rMK/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англиски (САД), меѓународен стил"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англиски (САД), Colemak стил"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англиски (САД), Dvorak стил"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Англиски (САД), Workman стил"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Германски"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француски"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француски (Канада)"</string>
diff --git a/packages/InputDevices/res/values-ml-rIN/strings.xml b/packages/InputDevices/res/values-ml-rIN/strings.xml
index 4f846a0..b42bdb7 100644
--- a/packages/InputDevices/res/values-ml-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ml-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ഇംഗ്ലീഷ് (യു.എസ്), അന്തർദ്ദേശീയ ശൈലി"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ഇംഗ്ലീഷ് (യു.എസ്), കോൽമാക് ശൈലി"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ഇംഗ്ലീഷ് (യു.എസ്), ദ്വരോക്ക് ശൈലി"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ജര്‍‌മ്മന്‍‌"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ഫ്രഞ്ച്"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ഫ്രഞ്ച് (കാനഡ)"</string>
diff --git a/packages/InputDevices/res/values-mn-rMN/strings.xml b/packages/InputDevices/res/values-mn-rMN/strings.xml
index a28fd2a..f1354fe 100644
--- a/packages/InputDevices/res/values-mn-rMN/strings.xml
+++ b/packages/InputDevices/res/values-mn-rMN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англи (АНУ), Олон улсын стиль"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англи (АНУ), Колемак стиль"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англи (АНУ), Дворак стиль"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Aнгли хэл (АНУ), Aжилчин загвар"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Герман"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Франц"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Франц (Канад)"</string>
diff --git a/packages/InputDevices/res/values-mr-rIN/strings.xml b/packages/InputDevices/res/values-mr-rIN/strings.xml
index 119ae6c..9ffcc70 100644
--- a/packages/InputDevices/res/values-mr-rIN/strings.xml
+++ b/packages/InputDevices/res/values-mr-rIN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"इंग्रजी (यूएस), आंतरराष्‍ट्रीय शैली"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"इंग्रजी (यूएस), कोलमॅक शैली"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"इंग्रजी (यूएस), ड्वोरॅक शैली"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"इंग्रजी (यूएस), कामगार शैली"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ्रेंच"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ्रेंच (कॅनडा)"</string>
diff --git a/packages/InputDevices/res/values-ms-rMY/strings.xml b/packages/InputDevices/res/values-ms-rMY/strings.xml
index a1a6d00..04983e3 100644
--- a/packages/InputDevices/res/values-ms-rMY/strings.xml
+++ b/packages/InputDevices/res/values-ms-rMY/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Bahasa Inggeris (AS), gaya A/bangsa"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Bahasa Inggeris (AS), gaya Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Bahasa Inggeris (AS), gaya Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Bahasa Inggeris (AS), gaya Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Bahasa Jerman"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Bahasa Perancis"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Bahasa Perancis (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-my-rMM/strings.xml b/packages/InputDevices/res/values-my-rMM/strings.xml
index 91bdc55..f3762b4 100644
--- a/packages/InputDevices/res/values-my-rMM/strings.xml
+++ b/packages/InputDevices/res/values-my-rMM/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"အင်္ဂလိပ်(ယူအက်စ်)၊အပြည်ပြည်ဆိုင်ရာပုံစံ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"အင်္ဂလိပ်(ယူအက်စ်)၊Colemak ပုံစံ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"အင်္ဂလိပ် (ယူအက်စ်)၊Dvorak ပုံစံ"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"အင်္ဂလိပ် (ယူအက်စ်)၊ အလုပ်လုပ်သူ ပုံစံ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ဂျာမန်"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ပြင်သစ်"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ပြင်သစ် (ကနေဒါ)"</string>
diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml
index ad4b704..378cdc1 100644
--- a/packages/InputDevices/res/values-nb/strings.xml
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), internasjonal stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ne-rNP/strings.xml b/packages/InputDevices/res/values-ne-rNP/strings.xml
index 4e5c2b5..571d717 100644
--- a/packages/InputDevices/res/values-ne-rNP/strings.xml
+++ b/packages/InputDevices/res/values-ne-rNP/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अङ्ग्रेजी (अमेरिकी), अन्तर्राष्ट्रिय शैली"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अङ्ग्रेजी (अमेरिकी), कोलमाक शैली"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अङ्ग्रेजी (अमेरिकी), डेभोर्याक शैली"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ्रान्सेली"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ्रेंच (क्यानाडा)"</string>
diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml
index c57251e..31ade05 100644
--- a/packages/InputDevices/res/values-nl/strings.xml
+++ b/packages/InputDevices/res/values-nl/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engels (VS), internationaal"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engels (VS), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engels (VS), Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engels (VS), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Duits"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frans"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frans (Canada)"</string>
diff --git a/packages/InputDevices/res/values-pa-rIN/strings.xml b/packages/InputDevices/res/values-pa-rIN/strings.xml
index a885088..437352c 100644
--- a/packages/InputDevices/res/values-pa-rIN/strings.xml
+++ b/packages/InputDevices/res/values-pa-rIN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ਅੰਗ੍ਰੇਜ਼ੀ (ਅਮਰੀਕਾ), ਅੰਤਰਰਾਸ਼ਟਰੀ ਸਟਾਈਲ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ਅੰਗ੍ਰੇਜ਼ੀ (ਅਮਰੀਕਾ), ਕੋਲਮਾਰਕ ਸਟਾਈਲ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ਅੰਗ੍ਰੇਜ਼ੀ (ਅਮਰੀਕਾ), ਵੋਰਕ ਸਟਾਈਲ"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ਅੰਗਰੇਜ਼ੀ (US), ਵਰਕਮੈਨ ਸਟਾਈਲ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ਜਰਮਨ"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ਫਰਾਂਸੀਸੀ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ਫ੍ਰੈਂਚ (ਕੈਨੇਡਾ)"</string>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index 39fb3ec..7175705 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angielski (USA), międzynarodowy"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angielski (USA), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angielski (USA), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Niemiecki"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francuski"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francuski (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml
index e9a0a38..15c2d37 100644
--- a/packages/InputDevices/res/values-pt-rBR/strings.xml
+++ b/packages/InputDevices/res/values-pt-rBR/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml
index 3ac3b84..b806fc2 100644
--- a/packages/InputDevices/res/values-pt-rPT/strings.xml
+++ b/packages/InputDevices/res/values-pt-rPT/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), est. Internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
index e9a0a38..15c2d37 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml
index c2392b1..ccfe568 100644
--- a/packages/InputDevices/res/values-ro/strings.xml
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engleză (SUA), stil internațional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engleză (SUA), stil Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engleză (SUA), stil Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Germană"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franceză"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franceză (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml
index 70ecf6e..7d3038e 100644
--- a/packages/InputDevices/res/values-ru/strings.xml
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"английский (США, международная)"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английский (США, Colemak)"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английский (США, Dvorak)"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немецкий"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"французский"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французский (Канада)"</string>
diff --git a/packages/InputDevices/res/values-si-rLK/strings.xml b/packages/InputDevices/res/values-si-rLK/strings.xml
index 67563c4..945a097 100644
--- a/packages/InputDevices/res/values-si-rLK/strings.xml
+++ b/packages/InputDevices/res/values-si-rLK/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ඉංග්‍රීසි (US), අන්තර්ජාතික ආකාරය"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ඉංග්‍රීසි (US), Colemak ආකාරය"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ඉංග්‍රීසි (US), Dvorak ආකාරය"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ඉංග්‍රීසි (එ.ජ.), වර්ක්මන් ආකාරය"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ජර්මානු"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ප්‍රංශ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ප්‍රංශ (කැනඩාව)"</string>
diff --git a/packages/InputDevices/res/values-sk/strings.xml b/packages/InputDevices/res/values-sk/strings.xml
index d2ee0cf..2e76024f 100644
--- a/packages/InputDevices/res/values-sk/strings.xml
+++ b/packages/InputDevices/res/values-sk/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), medzinárodné"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), štýl Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), štýl Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"anglické (USA), štýl Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemecké"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francúzske"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francúzske (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
index 38542ef..f9e4610 100644
--- a/packages/InputDevices/res/values-sl/strings.xml
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angleška (ZDA), mednarodni slog"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angleška (ZDA), slog Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angleška (ZDA), slog Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemška"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francoska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francoska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sq-rAL/strings.xml b/packages/InputDevices/res/values-sq-rAL/strings.xml
index 2092926..8a9000d 100644
--- a/packages/InputDevices/res/values-sq-rAL/strings.xml
+++ b/packages/InputDevices/res/values-sq-rAL/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglisht (SHBA), stili \"ndërkombëtar\""</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglisht (SHBA), stili \"colemak\""</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglisht (SHBA), stili \"dvorak\""</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"anglisht (SHBA), stili \"workman\""</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"gjermanisht"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"frëngjisht"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"frëngjisht (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml
index dd500e6..a216bc3 100644
--- a/packages/InputDevices/res/values-sr/strings.xml
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"енглеска (САД), међународни стил"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"енглеска (САД), Colemak стил"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"енглеска (САД), Dvorak стил"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немачка"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"француска"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"француска (Канада)"</string>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index c2406c0..23f2c98 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
index f71a696..720d427 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Kiingereza (Marekani), Muundo wa Kimataifa"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Kiingereza (Marekani), Muundo wa Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Kiingereza (Marekani), Muundo wa Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Kijerumani"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Kifaransa"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Kifaransa (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-ta-rIN/strings.xml b/packages/InputDevices/res/values-ta-rIN/strings.xml
index 30d653d..43d0bd1 100644
--- a/packages/InputDevices/res/values-ta-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ta-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ஆங்கிலம் (யூஎஸ்), சர்வதேச நடை"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ஆங்கிலம் (யூஎஸ்), கோல்மாக் நடை"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ஆங்கிலம் (யூஎஸ்), டிவாரக் நடை"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ஜெர்மன்"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ஃபிரெஞ்ச்"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ஃபிரெஞ்ச் (கனடா)"</string>
diff --git a/packages/InputDevices/res/values-te-rIN/strings.xml b/packages/InputDevices/res/values-te-rIN/strings.xml
index cc1b14d..d214ae6 100644
--- a/packages/InputDevices/res/values-te-rIN/strings.xml
+++ b/packages/InputDevices/res/values-te-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ఇంగ్లీష్ (US), అంతర్జాతీయ శైలి"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ఇంగ్లీష్ (US), కొల్‌మాక్ శైలి"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ఇంగ్లీష్ (US), ద్వోరక్ శైలి"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"జర్మన్"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ఫ్రెంచ్"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ఫ్రెంచ్ (కెనడా)"</string>
diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml
index 487b051..50dc1e4 100644
--- a/packages/InputDevices/res/values-th/strings.xml
+++ b/packages/InputDevices/res/values-th/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"อังกฤษ (อเมริกัน), รูปแบบนานาชาติ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"อังกฤษ (อเมริกัน), รูปแบบ Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"อังกฤษ (อเมริกัน), รูปแบบ Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"อังกฤษ (อเมริกัน), รูปแบบ Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"เยอรมัน"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ฝรั่งเศส"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ฝรั่งเศส (แคนาดา)"</string>
diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml
index d7920ed..4bd857d 100644
--- a/packages/InputDevices/res/values-tl/strings.xml
+++ b/packages/InputDevices/res/values-tl/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ingles (US), istilong International"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ingles (US), istilong Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ingles (US), istilong Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
index c0c70be..3dd0d3b 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilizce (ABD) Uluslararası stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilizce (ABD) Colemak stili"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilizce (ABD) Dvorak stili"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Almanca"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransızca"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransızca (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
index 43a3fe6..c818001 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"англійська (США), міжнародна"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"англійська (США), розкладка Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"англійська (США), розкладка Дворака"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"англійська (США), розкладка Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"німецька"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"французька"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французька (Канада)"</string>
diff --git a/packages/InputDevices/res/values-ur-rPK/strings.xml b/packages/InputDevices/res/values-ur-rPK/strings.xml
index eee1eb2..2c5a8b8 100644
--- a/packages/InputDevices/res/values-ur-rPK/strings.xml
+++ b/packages/InputDevices/res/values-ur-rPK/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگریزی (امریکہ)، انٹرنیشنل سٹائل"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگریزی (امریکہ)، کول مارک سٹائل"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگریزی (امریکہ)، ڈوراک سٹائل"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"جرمن"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسیسی"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسیسی (کینیڈا)"</string>
diff --git a/packages/InputDevices/res/values-uz-rUZ/strings.xml b/packages/InputDevices/res/values-uz-rUZ/strings.xml
index 3b6772b..ee20328 100644
--- a/packages/InputDevices/res/values-uz-rUZ/strings.xml
+++ b/packages/InputDevices/res/values-uz-rUZ/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglizcha (AQSH), xalqaro uslubda"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglizcha (AQSH), Kolemak uslubida"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglizcha (AQSH), Dvorak uslubida"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Nemischa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransuzcha"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransuzcha (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml
index a90c1cd..3ca715b 100644
--- a/packages/InputDevices/res/values-vi/strings.xml
+++ b/packages/InputDevices/res/values-vi/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Tiếng Anh (Mỹ), kiểu Quốc tế"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Tiếng Anh (Mỹ), kiểu Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Tiếng Anh (Mỹ), kiểu Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Tiếng Anh (Mỹ), kiểu Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tiếng Đức"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Tiếng Pháp"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Tiếng Pháp (Canada)"</string>
diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml
index c050ebd..2ba2667 100644
--- a/packages/InputDevices/res/values-zh-rCN/strings.xml
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英语(美国),国际风格"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英语(美国),Colemak 风格"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英语(美国),Dvorak 风格"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德语"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"法语"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法语(加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zh-rHK/strings.xml b/packages/InputDevices/res/values-zh-rHK/strings.xml
index ff5570e..9385df8 100644
--- a/packages/InputDevices/res/values-zh-rHK/strings.xml
+++ b/packages/InputDevices/res/values-zh-rHK/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英文 (美國),國際樣式"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英文 (美國),Colemak 樣式"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英文 (美國),Dvorak 樣式"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英文 (美國),Workman 樣式"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德文"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"法文"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法文 (加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zh-rTW/strings.xml b/packages/InputDevices/res/values-zh-rTW/strings.xml
index 859983d..89ef558 100644
--- a/packages/InputDevices/res/values-zh-rTW/strings.xml
+++ b/packages/InputDevices/res/values-zh-rTW/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英文 (美國),國際樣式"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英文 (美國),Colemak 樣式"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英文 (美國),Dvorak 樣式"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英文 (美國),Workman 樣式"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德文"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"法文"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法文 (加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml
index 9f30f7a..80899d0 100644
--- a/packages/InputDevices/res/values-zu/strings.xml
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"I-English (US), isitayela sakwamanye amazwe"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"I-English (US), isitayela se-Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"I-English (US), isitayela se-Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Isi-German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Isi-French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Isi-French (Canada)"</string>
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 968961a..5644c9a 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -21,6 +21,9 @@
     <!-- US English (Dvorak style) keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_english_us_dvorak_label">English (US), Dvorak style</string>
 
+    <!-- US English (Workman style) keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_english_us_workman_label">English (US), Workman style</string>
+
     <!-- German keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_german_label">German</string>
 
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 6f7253c..a302162 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -20,6 +20,10 @@
             android:label="@string/keyboard_layout_english_us_dvorak_label"
             android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak" />
 
+    <keyboard-layout android:name="keyboard_layout_english_us_workman"
+            android:label="@string/keyboard_layout_english_us_workman_label"
+            android:keyboardLayout="@raw/keyboard_layout_english_us_workman" />
+
     <keyboard-layout android:name="keyboard_layout_german"
             android:label="@string/keyboard_layout_german_label"
             android:keyboardLayout="@raw/keyboard_layout_german" />
diff --git a/packages/Keyguard/res/values-ne-rNP/strings.xml b/packages/Keyguard/res/values-ne-rNP/strings.xml
index d18d0f2..4a5907e0 100644
--- a/packages/Keyguard/res/values-ne-rNP/strings.xml
+++ b/packages/Keyguard/res/values-ne-rNP/strings.xml
@@ -82,22 +82,22 @@
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"तपाईँले तपाईँको अनलक ढाँचा गलत तरिकाले <xliff:g id="NUMBER_0">%d</xliff:g> पटक खिच्नु भएको छ। \n\n <xliff:g id="NUMBER_1">%d</xliff:g> सेकेन्डमा फेरि कोसिस गर्नुहोस्।"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="8774056606869646621">"तपाईंले गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g>पटक।  <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो ट्याब्लेट रिसेट हुनेछ जसले आफ्नो सम्पूर्ण डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="1843331751334128428">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER_0">%d</xliff:g> पटक। पछि <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो फोन  रिसेट हुनेछ जसले सम्पूर्ण डेटा मेटाउनेछ।।"</string>
-    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"तपाईं गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। यो ट्याब्लेट रिेसेट गरिनेछ जसले सम्पूर्ण डेटा मेट्नेछ।"</string>
+    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"तपाईँ गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। यो ट्याब्लेट रिेसेट गरिनेछ जसले सम्पूर्ण डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_now_wiping" product="default" msgid="7154028908459817066">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। यो फोन रिसेट गरिनेछ जसले सम्पूर्ण डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="6159955099372112688">"तपाईंले गलत तरिकाले  ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g> पटक।  <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो प्रयोगकर्ता हटाइनेछ जसले सम्पूर्ण प्रयोगकर्ता डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="6945823186629369880">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो प्रयोगकर्ता हटाइनेछ जसले  सबै प्रयोगकर्ता डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="3963486905355778734">"तपाईंले गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। यो प्रयोगकर्ता हटाइनेछ जसले सम्पूर्ण प्रयोगकर्ता डेटा मेट्नेछ ।"</string>
     <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="7729009752252111673">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। यो प्रयोगकर्ता हटाइनेछ जसले सम्पूर्ण प्रयोगकर्ता डेटा मेट्नेछ।"</string>
-    <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="4621778507387853694">"तपाईं गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, काम प्रोफाइल हटाइनेछ जसले सबै प्रोफाइल डेटा मेट्नेछ।"</string>
+    <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="4621778507387853694">"तपाईँ गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, काम प्रोफाइल हटाइनेछ जसले सबै प्रोफाइल डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="6853071165802933545">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि , काम प्रोफाइल हटाइनेछ जसले सबै प्रोफाइल डेटा मेट्नेछ।"</string>
-    <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"तपाईं गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। काम प्रोफाइल हटाइनेछ जसले सम्पूर्ण  प्रोफाइल डेटा मेट्नेछ ।"</string>
+    <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"तपाईँ गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। काम प्रोफाइल हटाइनेछ जसले सम्पूर्ण  प्रोफाइल डेटा मेट्नेछ ।"</string>
     <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4951507352869831265">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। काम प्रोफाइल हटाइनेछ जसले सम्पूर्ण प्रोफाइल डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"तपाईंले गलत तरिकाले आफ्नो अनलक ढाँचा <xliff:g id="NUMBER_0">%d</xliff:g> पटक कोर्नुभयो। <xliff:g id="NUMBER_1">%d</xliff:g> विफल प्रयत्नहरू पछि, तपाईंलाई आफ्नो ट्याब्लेट इमेल खाता प्रयोग गरेर अनलक गर्न सोधिने छ।\n\n फेरि प्रयास गर्नुहोस् <xliff:g id="NUMBER_2">%d</xliff:g> सेकेन्डहरूमा।"</string>
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"तपाईँले आफ्नो अनलक ढाँचा गलत रूपमा <xliff:g id="NUMBER_0">%d</xliff:g> पटक तान्नु भएको छ। <xliff:g id="NUMBER_1">%d</xliff:g> धेरै असफल प्रयासहरूपछि, तपाईँलाई एउटा इमेल खाताको प्रयोग गरेर तपाईँको फोन अनलक गर्न सोधिने छ।\n\n फेरि <xliff:g id="NUMBER_2">%d</xliff:g> सेकेन्डमा प्रयास गर्नुहोस्।"</string>
     <string name="kg_password_wrong_pin_code_pukked" msgid="30531039455764924">"SIM PIN कोड गलत छ। अब तपाईंले अाफ्नो उपकरण खोल्नलाई तपाईंको वाहकसँग सम्पर्क गर्नै पर्दर।"</string>
     <plurals name="kg_password_wrong_pin_code" formatted="false" msgid="6721575017538162249">
-      <item quantity="other"> गलत SIM PIN कोड, तपाईं सँग <xliff:g id="NUMBER_1">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
-      <item quantity="one">SIM PIN कोड गलत छ, तपाईंले अाफ्नो यन्त्र खोल्नलाई तपाईंको वाहकसँग सम्पर्क गर्नै पर्न अघि तपाईंसँग <xliff:g id="NUMBER_0">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
+      <item quantity="other"> गलत SIM PIN कोड, तपाईँ सँग <xliff:g id="NUMBER_1">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
+      <item quantity="one">SIM PIN कोड गलत छ, तपाईँले अाफ्नो यन्त्र खोल्नलाई तपाईँको वाहकसँग सम्पर्क गर्नै पर्न अघि तपाईँसँग <xliff:g id="NUMBER_0">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
     </plurals>
     <string name="kg_password_wrong_puk_code_dead" msgid="7077536808291316208">"SIM प्रयोग बिहिन छ। तपाईंको वाहकलाई सम्पर्क गर्नुहोस्।"</string>
     <plurals name="kg_password_wrong_puk_code" formatted="false" msgid="7576227366999858780">
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index 9a91ca4..29b319f 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -133,10 +133,6 @@
         mLockPatternView.setSaveEnabled(false);
         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
 
-        // stealth mode will be the same for the life of this screen
-        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
-
         // vibrate mode will be the same for the life of this screen
         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
 
@@ -176,6 +172,8 @@
     @Override
     public void reset() {
         // reset lock pattern
+        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+                KeyguardUpdateMonitor.getCurrentUser()));
         mLockPatternView.enableInput();
         mLockPatternView.setEnabled(true);
         mLockPatternView.clearPattern();
diff --git a/packages/MtpDocumentsProvider/res/values/strings.xml b/packages/MtpDocumentsProvider/res/values/strings.xml
index 37efdb5..7a4087b 100644
--- a/packages/MtpDocumentsProvider/res/values/strings.xml
+++ b/packages/MtpDocumentsProvider/res/values/strings.xml
@@ -14,7 +14,11 @@
      limitations under the License.
 -->
 
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Title of the external storage application [CHAR LIMIT=32] -->
     <string name="app_label">MTP Storage</string>
+    <!-- Name of MTP root shown in UI. Please align the two strings (device
+         model and storage name) in proper order in the language.
+         [CHAR LIMIT=32] -->
+    <string name="root_name"><xliff:g id="device_model" example="Nexus 9">%1$s</xliff:g> <xliff:g id="storage_name" example="Internal Storage">%2$s</xliff:g></string>
 </resources>
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java
index b5694b7..9e874bd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java
@@ -16,25 +16,25 @@
 
 package com.android.mtp;
 
+import android.content.res.Resources;
 import android.database.MatrixCursor;
+import android.media.MediaFile;
 import android.mtp.MtpConstants;
 import android.mtp.MtpObjectInfo;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 
-import java.util.Date;
-
 final class CursorHelper {
     static final int DUMMY_HANDLE_FOR_ROOT = 0;
 
     private CursorHelper() {
     }
 
-    static void addToCursor(MtpRoot root, MatrixCursor.RowBuilder builder) {
+    static void addToCursor(Resources resources, MtpRoot root, MatrixCursor.RowBuilder builder) {
         final Identifier identifier = new Identifier(
                 root.mDeviceId, root.mStorageId, DUMMY_HANDLE_FOR_ROOT);
         builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
-        builder.add(Document.COLUMN_DISPLAY_NAME, root.mDescription);
+        builder.add(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
         builder.add(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
         builder.add(Document.COLUMN_LAST_MODIFIED, null);
         builder.add(Document.COLUMN_FLAGS, 0);
@@ -71,30 +71,18 @@
     }
 
     static String formatTypeToMimeType(int format) {
-        // TODO: Add complete list of mime types.
-        switch (format) {
-            case MtpConstants.FORMAT_ASSOCIATION:
-                return DocumentsContract.Document.MIME_TYPE_DIR;
-            case MtpConstants.FORMAT_MP3:
-                return "audio/mp3";
-            case MtpConstants.FORMAT_EXIF_JPEG:
-                return "image/jpeg";
-            default:
-                return "application/octet-stream";
+        if (format == MtpConstants.FORMAT_ASSOCIATION) {
+            return DocumentsContract.Document.MIME_TYPE_DIR;
+        } else {
+            return MediaFile.getMimeTypeForFormatCode(format);
         }
     }
 
-    static int mimeTypeToFormatType(String mimeType) {
-        // TODO: Add complete list of mime types.
-        switch (mimeType.toLowerCase()) {
-            case Document.MIME_TYPE_DIR:
-                return MtpConstants.FORMAT_ASSOCIATION;
-            case "audio/mp3":
-                return MtpConstants.FORMAT_MP3;
-            case "image/jpeg":
-                return MtpConstants.FORMAT_EXIF_JPEG;
-            default:
-                return MtpConstants.FORMAT_UNDEFINED;
+    static int mimeTypeToFormatType(String fileName, String mimeType) {
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+            return MtpConstants.FORMAT_ASSOCIATION;
+        } else {
+            return MediaFile.getFormatCode(fileName, mimeType);
         }
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
new file mode 100644
index 0000000..11e937b
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -0,0 +1,161 @@
+package com.android.mtp;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Database for MTP objects.
+ * The object handle which is identifier for object in MTP protocol is not stable over sessions.
+ * When we resume the process, we need to remap our document ID with MTP's object handle.
+ * The database object remembers the map of document ID and fullpath, and helps to remap object
+ * handle and document ID by comparing fullpath.
+ * TODO: Remove @VisibleForTesting annotation when we start to use this class.
+ */
+@VisibleForTesting
+class MtpDatabase {
+    private static final int VERSION = 1;
+    private static final String NAME = "mtp";
+
+    private static final String TABLE_MTP_DOCUMENTS = "MtpDocuments";
+
+    static final String COLUMN_DEVICE_ID = "deviceId";
+    static final String COLUMN_STORAGE_ID = "storageId";
+    static final String COLUMN_OBJECT_HANDLE = "objectHandle";
+    static final String COLUMN_FULL_PATH = "fullPath";
+
+    private static class OpenHelper extends SQLiteOpenHelper {
+        private static final String CREATE_TABLE_QUERY =
+                "CREATE TABLE " + TABLE_MTP_DOCUMENTS + " (" +
+                DocumentsContract.Document.COLUMN_DOCUMENT_ID +
+                    " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
+                COLUMN_STORAGE_ID + " INTEGER NOT NULL," +
+                COLUMN_OBJECT_HANDLE + " INTEGER," +
+                COLUMN_FULL_PATH + " TEXT NOT NULL," +
+                DocumentsContract.Document.COLUMN_MIME_TYPE + " TEXT," +
+                DocumentsContract.Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
+                DocumentsContract.Document.COLUMN_SUMMARY + " TEXT," +
+                DocumentsContract.Document.COLUMN_LAST_MODIFIED + " INTEGER," +
+                DocumentsContract.Document.COLUMN_ICON + " INTEGER," +
+                DocumentsContract.Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
+                DocumentsContract.Document.COLUMN_SIZE + " INTEGER NOT NULL);";
+
+        public OpenHelper(Context context) {
+            super(context, NAME, null, VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL(CREATE_TABLE_QUERY);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private final SQLiteDatabase database;
+
+    @VisibleForTesting
+    MtpDatabase(Context context) {
+        final OpenHelper helper = new OpenHelper(context);
+        database = helper.getWritableDatabase();
+    }
+
+    @VisibleForTesting
+    static void deleteDatabase(Context context) {
+        SQLiteDatabase.deleteDatabase(context.getDatabasePath(NAME));
+    }
+
+    @VisibleForTesting
+    Cursor queryChildDocuments(String[] columnNames) {
+        return database.query(TABLE_MTP_DOCUMENTS, columnNames, null, null, null, null, null);
+    }
+
+    @VisibleForTesting
+    void putRootDocument(MtpRoot root) throws Exception {
+        database.beginTransaction();
+        try {
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_DEVICE_ID, root.mDeviceId);
+            values.put(COLUMN_STORAGE_ID, root.mStorageId);
+            values.putNull(COLUMN_OBJECT_HANDLE);
+            values.put(
+                    COLUMN_FULL_PATH, "/" + root.mDeviceId + "/" + escape(root.mDescription));
+            values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
+            values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription);
+            values.putNull(Document.COLUMN_SUMMARY);
+            values.putNull(Document.COLUMN_LAST_MODIFIED);
+            values.putNull(Document.COLUMN_ICON);
+            values.put(Document.COLUMN_FLAGS, 0);
+            values.put(Document.COLUMN_SIZE,
+                    (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
+            if (database.insert(TABLE_MTP_DOCUMENTS, null, values) == -1) {
+                throw new Exception("Failed to add root document.");
+            }
+            database.setTransactionSuccessful();
+        } finally {
+            database.endTransaction();
+        }
+    }
+
+    @VisibleForTesting
+    void putDocument(int deviceId, String parentFullPath, MtpObjectInfo info) throws Exception {
+        database.beginTransaction();
+        try {
+            final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
+
+            int flag = 0;
+            if (info.getProtectionStatus() == 0) {
+                flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
+                        DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
+                if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
+                    flag |= DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
+                }
+            }
+            if (info.getThumbCompressedSize() > 0) {
+                flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
+            }
+
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_DEVICE_ID, deviceId);
+            values.put(COLUMN_STORAGE_ID, info.getStorageId());
+            values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
+            values.put(COLUMN_FULL_PATH, parentFullPath + "/" + escape(info.getName()));
+            values.put(
+                    Document.COLUMN_MIME_TYPE, CursorHelper.formatTypeToMimeType(info.getFormat()));
+            values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
+            values.putNull(Document.COLUMN_SUMMARY);
+            values.put(
+                    Document.COLUMN_LAST_MODIFIED,
+                    info.getDateModified() != 0 ? info.getDateModified() : null);
+            values.putNull(Document.COLUMN_ICON);
+            values.put(Document.COLUMN_FLAGS, flag);
+            values.put(Document.COLUMN_SIZE, info.getCompressedSize());
+            if (database.insert(TABLE_MTP_DOCUMENTS, null, values) == -1) {
+                throw new Exception("Failed to add document.");
+            }
+            database.setTransactionSuccessful();
+        } finally {
+            database.endTransaction();
+        }
+    }
+
+    @VisibleForTesting
+    private String escape(String s) throws UnsupportedEncodingException {
+        return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
+    }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index a1a43c1..7883e61 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -18,6 +18,7 @@
 
 import android.content.ContentResolver;
 import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.graphics.Point;
@@ -59,8 +60,8 @@
     private MtpManager mMtpManager;
     private ContentResolver mResolver;
     private Map<Integer, DeviceToolkit> mDeviceToolkits;
-    private DocumentLoader mDocumentLoaders;
     private RootScanner mRootScanner;
+    private Resources mResources;
 
     /**
      * Provides singleton instance to MtpDocumentsService.
@@ -72,6 +73,7 @@
     @Override
     public boolean onCreate() {
         sSingleton = this;
+        mResources = getContext().getResources();
         mMtpManager = new MtpManager(getContext());
         mResolver = getContext().getContentResolver();
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
@@ -80,7 +82,8 @@
     }
 
     @VisibleForTesting
-    void onCreateForTesting(MtpManager mtpManager, ContentResolver resolver) {
+    void onCreateForTesting(Resources resources, MtpManager mtpManager, ContentResolver resolver) {
+        mResources = resources;
         mMtpManager = mtpManager;
         mResolver = resolver;
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
@@ -99,10 +102,8 @@
             final MatrixCursor.RowBuilder builder = cursor.newRow();
             builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId());
             builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
-            builder.add(Root.COLUMN_TITLE, root.mDescription);
-            builder.add(
-                    Root.COLUMN_DOCUMENT_ID,
-                    rootIdentifier.toDocumentId());
+            builder.add(Root.COLUMN_TITLE, root.getRootName(mResources));
+            builder.add(Root.COLUMN_DOCUMENT_ID, rootIdentifier.toDocumentId());
             builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace);
         }
         cursor.setNotificationUri(
@@ -143,7 +144,7 @@
                 if (identifier.mStorageId != root.mStorageId)
                     continue;
                 final MatrixCursor cursor = new MatrixCursor(projection);
-                CursorHelper.addToCursor(root, cursor.newRow());
+                CursorHelper.addToCursor(mResources, root, cursor.newRow());
                 return cursor;
             }
         }
@@ -241,7 +242,7 @@
                     new MtpObjectInfo.Builder()
                             .setStorageId(parentId.mStorageId)
                             .setParent(parentId.mObjectHandle)
-                            .setFormat(CursorHelper.mimeTypeToFormatType(mimeType))
+                            .setFormat(CursorHelper.mimeTypeToFormatType(displayName, mimeType))
                             .setName(displayName)
                             .build(), pipe[1]);
             final String documentId = new Identifier(parentId.mDeviceId, parentId.mStorageId,
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index af7f691..714936d 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -107,7 +107,10 @@
             }
             final MtpRoot[] results = new MtpRoot[storageIds.length];
             for (int i = 0; i < storageIds.length; i++) {
-                results[i] = new MtpRoot(deviceId, device.getStorageInfo(storageIds[i]));
+                results[i] = new MtpRoot(
+                        device.getDeviceId(),
+                        device.getDeviceInfo().getModel(),
+                        device.getStorageInfo(storageIds[i]));
             }
             return results;
         }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
index 9dd53c0..ec338c3 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
@@ -16,6 +16,7 @@
 
 package com.android.mtp;
 
+import android.content.res.Resources;
 import android.mtp.MtpStorageInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -23,6 +24,7 @@
 class MtpRoot {
     final int mDeviceId;
     final int mStorageId;
+    final String mDeviceModelName;
     final String mDescription;
     final long mFreeSpace;
     final long mMaxCapacity;
@@ -31,21 +33,24 @@
     @VisibleForTesting
     MtpRoot(int deviceId,
             int storageId,
+            String deviceName,
             String description,
             long freeSpace,
             long maxCapacity,
             String volumeIdentifier) {
         mDeviceId = deviceId;
         mStorageId = storageId;
+        mDeviceModelName = deviceName;
         mDescription = description;
         mFreeSpace = freeSpace;
         mMaxCapacity = maxCapacity;
         mVolumeIdentifier = volumeIdentifier;
     }
 
-    MtpRoot(int deviceId, MtpStorageInfo storageInfo) {
+    MtpRoot(int deviceId, String deviceModelName, MtpStorageInfo storageInfo) {
         mDeviceId = deviceId;
         mStorageId = storageInfo.getStorageId();
+        mDeviceModelName = deviceModelName;
         mDescription = storageInfo.getDescription();
         mFreeSpace = storageInfo.getFreeSpace();
         mMaxCapacity = storageInfo.getMaxCapacity();
@@ -59,6 +64,7 @@
         final MtpRoot other = (MtpRoot) object;
         return mDeviceId == other.mDeviceId &&
                 mStorageId == other.mStorageId &&
+                mDeviceModelName.equals(other.mDeviceModelName) &&
                 mDescription.equals(other.mDescription) &&
                 mFreeSpace == other.mFreeSpace &&
                 mMaxCapacity == other.mMaxCapacity &&
@@ -67,7 +73,19 @@
 
     @Override
     public int hashCode() {
-        return mDeviceId ^ mStorageId ^ mDescription.hashCode() ^ ((int) mFreeSpace) ^
-                ((int) mMaxCapacity) ^ mVolumeIdentifier.hashCode();
+        return mDeviceId ^ mStorageId ^ mDeviceModelName.hashCode() ^ mDescription.hashCode() ^
+                ((int) mFreeSpace) ^ ((int) mMaxCapacity) ^ mVolumeIdentifier.hashCode();
+    }
+    
+    @Override
+    public String toString() {
+        return "MtpRoot{Name: " + mDeviceModelName + " " + mDescription + "}";
+    }
+    
+    String getRootName(Resources resources) {
+        return String.format(
+                resources.getString(R.string.root_name),
+                mDeviceModelName,
+                mDescription);
     }
 }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
new file mode 100644
index 0000000..7ce32542
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -0,0 +1,121 @@
+package com.android.mtp;
+
+
+import android.database.Cursor;
+import android.mtp.MtpConstants;
+import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class MtpDatabaseTest extends AndroidTestCase {
+    private final String[] COLUMN_NAMES = new String[] {
+        DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+        MtpDatabase.COLUMN_DEVICE_ID,
+        MtpDatabase.COLUMN_STORAGE_ID,
+        MtpDatabase.COLUMN_OBJECT_HANDLE,
+        MtpDatabase.COLUMN_FULL_PATH,
+        DocumentsContract.Document.COLUMN_MIME_TYPE,
+        DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+        DocumentsContract.Document.COLUMN_SUMMARY,
+        DocumentsContract.Document.COLUMN_LAST_MODIFIED,
+        DocumentsContract.Document.COLUMN_ICON,
+        DocumentsContract.Document.COLUMN_FLAGS,
+        DocumentsContract.Document.COLUMN_SIZE
+    };
+
+    @Override
+    public void tearDown() {
+        MtpDatabase.deleteDatabase(getContext());
+    }
+
+    public void testPutRootDocument() throws Exception {
+        final MtpDatabase database = new MtpDatabase(getContext());
+        final MtpRoot root = new MtpRoot(
+                0,
+                1,
+                "Device A",
+                "Storage",
+                1000,
+                2000,
+                "");
+        database.putRootDocument(root);
+
+        final MtpRoot duplicatedNameRoot = new MtpRoot(
+                0,
+                2,
+                "Device A",
+                "Storage",
+                1000,
+                2000,
+                "");
+        database.putRootDocument(duplicatedNameRoot);
+
+        final MtpRoot strangeNameRoot = new MtpRoot(
+                0,
+                3,
+                "Device A",
+                "/@#%&<>Storage",
+                1000,
+                2000,
+                "");
+        database.putRootDocument(strangeNameRoot);
+
+        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
+        assertEquals(3, cursor.getCount());
+
+        cursor.moveToNext();
+        assertEquals("documentId", 1, cursor.getInt(0));
+        assertEquals("deviceId", 0, cursor.getInt(1));
+        assertEquals("storageId", 1, cursor.getInt(2));
+        assertTrue("objectHandle", cursor.isNull(3));
+        assertEquals("fullPath", "/0/Storage", cursor.getString(4));
+        assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(5));
+        assertEquals("displayName", "Storage", cursor.getString(6));
+        assertTrue("summary", cursor.isNull(7));
+        assertTrue("lastModified", cursor.isNull(8));
+        assertTrue("icon", cursor.isNull(9));
+        assertEquals("flag", 0, cursor.getInt(10));
+        assertEquals("size", 1000, cursor.getInt(11));
+
+        cursor.moveToNext();
+        assertEquals("documentId", 2, cursor.getInt(0));
+        assertEquals("fullPath", "/0/Storage", cursor.getString(4));
+
+        cursor.moveToNext();
+        assertEquals("documentId", 3, cursor.getInt(0));
+        assertEquals("fullPath", "/0/%2F%40%23%25%26%3C%3EStorage", cursor.getString(4));
+    }
+
+    public void testPutDocument() throws Exception {
+        final MtpDatabase database = new MtpDatabase(getContext());
+        final MtpObjectInfo.Builder builder = new MtpObjectInfo.Builder();
+        builder.setObjectHandle(100);
+        builder.setName("test.txt");
+        builder.setStorageId(5);
+        builder.setFormat(MtpConstants.FORMAT_TEXT);
+        builder.setCompressedSize(1000);
+        database.putDocument(0, "/0/Storage", builder.build());
+
+        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
+        assertEquals(1, cursor.getCount());
+        cursor.moveToNext();
+        assertEquals("documentId", 1, cursor.getInt(0));
+        assertEquals("deviceId", 0, cursor.getInt(1));
+        assertEquals("storageId", 5, cursor.getInt(2));
+        assertEquals("objectHandle", 100, cursor.getInt(3));
+        assertEquals("fullPath", "/0/Storage/test.txt", cursor.getString(4));
+        assertEquals("mimeType", "text/plain", cursor.getString(5));
+        assertEquals("displayName", "test.txt", cursor.getString(6));
+        assertTrue("summary", cursor.isNull(7));
+        assertTrue("lastModified", cursor.isNull(8));
+        assertTrue("icon", cursor.isNull(9));
+        assertEquals(
+                "flag",
+                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
+                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
+                cursor.getInt(10));
+        assertEquals("size", 1000, cursor.getInt(11));
+    }
+}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 4b3a5cd..0c03814c 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -16,19 +16,21 @@
 
 package com.android.mtp;
 
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
 import android.mtp.MtpConstants;
-import android.mtp.MtpObjectInfo.Builder;
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsContract;
 import android.test.AndroidTestCase;
+import android.test.mock.MockResources;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.util.Date;
 
 @SmallTest
 public class MtpDocumentsProviderTest extends AndroidTestCase {
@@ -37,13 +39,23 @@
     private TestContentResolver mResolver;
     private MtpDocumentsProvider mProvider;
     private TestMtpManager mMtpManager;
+    private final MockResources mResources = new MockResources() {
+        @Override
+        public String getString(int id) throws NotFoundException {
+            switch (id) {
+                case R.string.root_name:
+                    return "%1$s %2$s";
+            }
+            throw new NotFoundException();
+        }
+    };
 
     @Override
     public void setUp() throws IOException {
         mResolver = new TestContentResolver();
         mMtpManager = new TestMtpManager(getContext());
         mProvider = new MtpDocumentsProvider();
-        mProvider.onCreateForTesting(mMtpManager, mResolver);
+        mProvider.onCreateForTesting(mResources, mMtpManager, mResolver);
     }
 
     public void testOpenAndCloseDevice() throws Exception {
@@ -52,6 +64,7 @@
                 new MtpRoot(
                         0 /* deviceId */,
                         1 /* storageId */,
+                        "Device A" /* device model name */,
                         "Storage A" /* volume description */,
                         1024 /* free space */,
                         2048 /* total space */,
@@ -86,6 +99,7 @@
                 new MtpRoot(
                         0 /* deviceId */,
                         1 /* storageId */,
+                        "Device A" /* device model name */,
                         "Storage A" /* volume description */,
                         1024 /* free space */,
                         2048 /* total space */,
@@ -101,6 +115,7 @@
                 new MtpRoot(
                         0 /* deviceId */,
                         1 /* storageId */,
+                        "Device A" /* device model name */,
                         "Storage A" /* volume description */,
                         1024 /* free space */,
                         2048 /* total space */,
@@ -121,6 +136,7 @@
                 new MtpRoot(
                         0 /* deviceId */,
                         1 /* storageId */,
+                        "Device A" /* device model name */,
                         "Storage A" /* volume description */,
                         1024 /* free space */,
                         2048 /* total space */,
@@ -130,6 +146,7 @@
                 new MtpRoot(
                         1 /* deviceId */,
                         1 /* storageId */,
+                        "Device B" /* device model name */,
                         "Storage B" /* volume description */,
                         2048 /* free space */,
                         4096 /* total space */,
@@ -146,7 +163,7 @@
             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
             // TODO: Add storage icon for MTP devices.
             assertTrue(cursor.isNull(2) /* icon */);
-            assertEquals("Storage A", cursor.getString(3));
+            assertEquals("Device A Storage A", cursor.getString(3));
             assertEquals("0_1_0", cursor.getString(4));
             assertEquals(1024, cursor.getInt(5));
         }
@@ -162,7 +179,7 @@
             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
             // TODO: Add storage icon for MTP devices.
             assertTrue(cursor.isNull(2) /* icon */);
-            assertEquals("Storage B", cursor.getString(3));
+            assertEquals("Device B Storage B", cursor.getString(3));
             assertEquals("1_1_0", cursor.getString(4));
             assertEquals(2048, cursor.getInt(5));
         }
@@ -183,6 +200,7 @@
                 new MtpRoot(
                         1 /* deviceId */,
                         1 /* storageId */,
+                        "Device B" /* device model name */,
                         "Storage B" /* volume description */,
                         2048 /* free space */,
                         4096 /* total space */,
@@ -200,7 +218,7 @@
             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
             // TODO: Add storage icon for MTP devices.
             assertTrue(cursor.isNull(2) /* icon */);
-            assertEquals("Storage B", cursor.getString(3));
+            assertEquals("Device B Storage B", cursor.getString(3));
             assertEquals("1_1_0", cursor.getString(4));
             assertEquals(2048, cursor.getInt(5));
         }
@@ -265,6 +283,7 @@
                 new MtpRoot(
                         0 /* deviceId */,
                         1 /* storageId */,
+                        "Device A" /* device model name */,
                         "Storage A" /* volume description */,
                         1024 /* free space */,
                         4096 /* total space */,
@@ -276,7 +295,7 @@
         cursor.moveToNext();
         assertEquals("0_1_0", cursor.getString(0));
         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
-        assertEquals("Storage A", cursor.getString(2));
+        assertEquals("Device A Storage A", cursor.getString(2));
         assertTrue(cursor.isNull(3));
         assertEquals(0, cursor.getInt(4));
         assertEquals(3072, cursor.getInt(5));
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 5605388..3d92cc2 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -18,12 +18,10 @@
 
 import android.content.Context;
 import android.mtp.MtpObjectInfo;
-import android.mtp.MtpObjectInfo.Builder;
 import android.os.ParcelFileDescriptor;
 
 import java.io.IOException;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 0380e21..f935f31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
 import android.content.Context;
 import android.os.ParcelUuid;
 import android.util.Log;
@@ -106,6 +107,10 @@
         return mAdapter.getScanMode();
     }
 
+    public BluetoothLeScanner getBluetoothLeScanner() {
+        return mAdapter.getBluetoothLeScanner();
+    }
+
     public int getState() {
         return mAdapter.getState();
     }
diff --git a/packages/Shell/res/values-sv/strings.xml b/packages/Shell/res/values-sv/strings.xml
index 47ee2f7..8b72938 100644
--- a/packages/Shell/res/values-sv/strings.xml
+++ b/packages/Shell/res/values-sv/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="3701846017049540910">"Skal"</string>
     <string name="bugreport_finished_title" msgid="2293711546892863898">"Felrapporten har skapats"</string>
-    <string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Dra till vänster om du vill dela felrapporten"</string>
+    <string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Svep åt vänster om du vill dela felrapporten"</string>
     <string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tryck om du vill dela felrapporten"</string>
     <string name="bugreport_confirm" msgid="5130698467795669780">"Felrapporter innehåller data från systemets olika loggfiler, inklusive personliga och privata uppgifter. Dela bara felrapporter med personer du litar på."</string>
     <string name="bugreport_confirm_repeat" msgid="4926842460688645058">"Visa det här meddelandet nästa gång"</string>
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 314b3c4..0cc2a67 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -3,7 +3,7 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) \
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) $(call all-Iaidl-files-under, src) \
     src/com/android/systemui/EventLogTags.logtags
 
 LOCAL_STATIC_JAVA_LIBRARIES := Keyguard
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 8fc7ad0..5d622a0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -127,6 +127,9 @@
     <!-- Assist -->
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
 
+    <!-- Listen for keyboard attachment / detachment -->
+    <uses-permission android:name="android.permission.TABLET_MODE" />
+
     <!-- Self permission for internal broadcasts. -->
     <permission android:name="com.android.systemui.permission.SELF"
             android:protectionLevel="signature" />
@@ -196,6 +199,11 @@
                     android:value="com.android.settings.category.system" />
         </activity>
 
+        <!-- Service used by secondary users to register themselves with the system user. -->
+        <service android:name=".recents.RecentsSystemUserService"
+            android:exported="false"
+            android:permission="com.android.systemui.permission.SELF" />
+
         <!-- Alternate Recents -->
         <activity android:name=".recents.RecentsActivity"
                   android:label="@string/accessibility_desc_recent_apps"
@@ -212,17 +220,6 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name=".recents.RecentsUserEventProxyReceiver"
-                  android:exported="false">
-            <intent-filter>
-                <action android:name="com.android.systemui.recents.action.SHOW_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.HIDE_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.TOGGLE_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.PRELOAD_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.CONFIG_CHANGED_FOR_USER" />
-            </intent-filter>
-        </receiver>
-
         <!-- Callback for dismissing screenshot notification after a share target is picked -->
         <receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
                   android:process=":screenshot"
diff --git a/packages/SystemUI/res/layout/qs_add_tile_layout.xml b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
new file mode 100644
index 0000000..962b00e
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:paddingTop="20dp"
+    android:paddingStart="7dp"
+    android:paddingEnd="7dp">
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="80dp"
+        android:orientation="vertical">
+        <ImageView
+            android:id="@+id/tile_icon"
+            android:layout_gravity="center"
+            android:layout_width="@dimen/qs_tile_icon_size"
+            android:layout_height="@dimen/qs_tile_icon_size" />
+        <TextView
+            android:id="@+id/tile_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center|bottom"
+            android:paddingTop="10dp"
+            android:gravity="center"
+            android:textSize="@dimen/qs_tile_text_size" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_add_tiles_list.xml b/packages/SystemUI/res/layout/qs_add_tiles_list.xml
new file mode 100644
index 0000000..312c207
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_add_tiles_list.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ListView
+            android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:paddingTop="10dp"
+            android:id="@+id/empty_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text="@string/no_tiles_add" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml
index 59fed5b..f430fa5 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel.xml
@@ -19,14 +19,13 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    android:paddingBottom="@dimen/navigation_bar_size"
     android:background="@drawable/qs_customizer_background"
     android:gravity="center_horizontal">
 
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="?android:attr/colorPrimary">
+        android:background="@drawable/notification_header_bg">
 
         <LinearLayout
             android:id="@+id/drag_buttons"
@@ -72,8 +71,8 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:navigationContentDescription="@*android:string/action_bar_up_description"
-            style="?android:attr/toolbarStyle"
-            android:background="?android:attr/colorPrimary" />
+            android:background="@drawable/notification_header_bg"
+            style="?android:attr/toolbarStyle" />
     </FrameLayout>
 
     <com.android.systemui.tuner.AutoScrollView
@@ -105,4 +104,10 @@
         android:elevation="@dimen/fab_elevation"
         android:background="@drawable/fab_background" />
 
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/navigation_bar_size"
+        android:layout_gravity="bottom"
+        android:background="#ff000000" />
+
 </com.android.systemui.qs.customize.QSCustomizer>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index d778c98..6187070 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-** Copyright 2015, The Android Open Source Project
+** Copyright 2012, 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. 
@@ -23,6 +23,8 @@
     android:layout_width="@dimen/notification_panel_width"
     android:layout_height="@dimen/status_bar_header_height"
     android:layout_gravity="@integer/notification_panel_layout_gravity"
+    android:paddingStart="@dimen/notification_side_padding"
+    android:paddingEnd="@dimen/notification_side_padding"
     android:baselineAligned="false"
     android:elevation="4dp"
     android:background="@drawable/notification_header_bg"
@@ -30,55 +32,21 @@
     android:focusable="true"
     >
 
+    <com.android.systemui.qs.QuickQSPanel
+        android:id="@+id/quick_qs_panel"
+        android:background="#0000"
+        android:layout_width="142dp"
+        android:layout_height="match_parent"
+        android:layout_alignParentEnd="true" />
+
     <LinearLayout
         android:id="@+id/expanded_group"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:paddingEnd="12dp"
-        android:orientation="horizontal">
-
-        <com.android.systemui.statusbar.AlphaOptimizedButton android:id="@+id/alarm_status"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="4dp"
-            android:drawablePadding="6dp"
-            android:drawableStart="@drawable/ic_access_alarms_small"
-            android:textColor="#64ffffff"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
-            android:paddingEnd="6dp"
-            android:paddingStart="6dp"
-            android:paddingTop="16dp"
-            android:paddingBottom="16dp"
-            android:background="?android:attr/selectableItemBackground"
-            android:visibility="gone"
-            />
-
-        <com.android.systemui.statusbar.policy.DateView android:id="@+id/date"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:singleLine="true"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
-            android:layout_below="@id/clock"
-            systemui:datePattern="EEE"
-            android:gravity="center_vertical"
-            android:textSize="20sp"
-            android:paddingTop="16dp"
-            android:layout_marginBottom="@dimen/clock_collapsed_bottom_margin" />
-
-        <include layout="@layout/split_clock_view"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="16dp"
-            android:id="@+id/clock"
-            />
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1" />
-
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:orientation="horizontal"
+        android:layout_alignParentEnd="true">
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
             android:id="@+id/settings_button_container"
             android:layout_width="48dp"
@@ -103,8 +71,57 @@
                 android:src="@drawable/tuner" />
 
         </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="match_parent"
+            android:src="@drawable/ic_expand_less"
+            android:tint="@android:color/white" />
     </LinearLayout>
 
+    <FrameLayout
+        android:id="@+id/date_group"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/clock_collapsed_bottom_margin"
+        android:layout_alignParentBottom="true">
+        <com.android.systemui.statusbar.policy.DateView android:id="@+id/date_collapsed"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:singleLine="true"
+            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
+            android:layout_below="@id/clock"
+            systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
+            />
+    </FrameLayout>
+
+    <include layout="@layout/split_clock_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_above="@id/date_group"
+        android:id="@+id/clock"
+        />
+
+    <com.android.systemui.statusbar.AlphaOptimizedButton android:id="@+id/alarm_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_toEndOf="@id/date_group"
+        android:layout_marginBottom="4dp"
+        android:drawablePadding="6dp"
+        android:drawableStart="@drawable/ic_access_alarms_small"
+        android:textColor="#64ffffff"
+        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
+        android:paddingEnd="6dp"
+        android:paddingStart="6dp"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:background="?android:attr/selectableItemBackground"
+        android:visibility="gone"
+        />
+
     <include
         android:id="@+id/qs_detail_header"
         layout="@layout/qs_detail_header"
@@ -113,12 +130,6 @@
         android:layout_alignParentBottom="true"
         />
 
-    <com.android.systemui.qs.QuickQSPanel
-        android:id="@+id/quick_qs_panel"
-        android:background="#0000"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
     <com.android.systemui.statusbar.AlphaOptimizedImageView
         android:id="@+id/qs_detail_header_progress"
         android:src="@drawable/indeterminate_anim"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index a64dbbd..bfa13e2 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -102,8 +102,7 @@
         android:id="@+id/status_bar_header"
         android:layout_width="@dimen/notification_panel_width"
         android:layout_height="@dimen/status_bar_header_height"
-        android:layout_marginStart="@dimen/notification_side_padding"
-        android:layout_marginEnd="@dimen/notification_side_padding" />
+        android:layout_gravity="@integer/notification_panel_layout_gravity" />
 
     <com.android.systemui.statusbar.AlphaOptimizedView
         android:id="@+id/qs_navbar_scrim"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index dd75dbf..5eca471 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -23,6 +23,8 @@
     android:layout_width="@dimen/notification_panel_width"
     android:layout_height="@dimen/status_bar_header_height"
     android:layout_gravity="@integer/notification_panel_layout_gravity"
+    android:paddingStart="@dimen/notification_side_padding"
+    android:paddingEnd="@dimen/notification_side_padding"
     android:baselineAligned="false"
     android:elevation="4dp"
     android:background="@drawable/notification_header_bg"
diff --git a/packages/SystemUI/res/layout/tile_listing.xml b/packages/SystemUI/res/layout/tile_listing.xml
new file mode 100644
index 0000000..9ab62ca
--- /dev/null
+++ b/packages/SystemUI/res/layout/tile_listing.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ** Copyright 2015, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License"); 
+ ** you may not use this file except in compliance with the License. 
+ ** You may obtain a copy of the License at 
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0 
+ **
+ ** Unless required by applicable law or agreed to in writing, software 
+ ** distributed under the License is distributed on an "AS IS" BASIS, 
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ ** See the License for the specific language governing permissions and 
+ ** limitations under the License.
+ -->
+
+ <LinearLayout
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="match_parent"
+     android:layout_height="wrap_content"
+     android:background="@drawable/qs_background_primary"
+     android:paddingBottom="20dp"
+     android:orientation="vertical">
+
+     <LinearLayout
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:background="@drawable/notification_header_bg"
+         android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+         android:paddingTop="10dp"
+         android:paddingBottom="10dp"
+         android:orientation="horizontal">
+         <ImageView
+             android:id="@android:id/icon"
+             android:layout_width="36dp"
+             android:layout_height="36dp" />
+         <TextView
+             android:id="@android:id/title"
+             android:paddingStart="10dp"
+             android:textColor="@android:color/white"
+             android:textAppearance="?android:attr/textAppearanceSmall"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_gravity="center_vertical" />
+     </LinearLayout>
+
+     <GridLayout
+         android:id="@+id/tile_grid"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:columnCount="4" />
+
+ </LinearLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 4d07cdb..41ed64c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Herrangskik Kitsinstellings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Wys helderheid in Kitsinstellings"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimenteel"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Skakel Bluetooth aan?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Jy moet Bluetooth aanskakel om jou sleutelbord aan jou tablet te koppel."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Skakel aan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index c1180b6..673710d 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ፈጣን ቅንብሮችን ዳግም ያደራጁ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"በፈጣን ቅንብሮች ውስጥ ብሩህነትን አሳይ"</string>
     <string name="experimental" msgid="6198182315536726162">"የሙከራ"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ብሉቱዝ ይብራ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"የቁልፍ ሰሌዳዎን ከእርስዎ ጡባዊ ጋር ለማገናኘት በመጀመሪያ ብሉቱዝን ማብራት አለብዎት።"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"አብራ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index fc2941f..c2c7e65 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -444,4 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"إعادة ترتيب الإعدادات السريعة"</string>
     <string name="show_brightness" msgid="6613930842805942519">"عرض السطوع في الإعدادات السريعة"</string>
     <string name="experimental" msgid="6198182315536726162">"إعدادات تجريبية"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"تشغيل البلوتوث؟"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"لتوصيل لوحة المفاتيح بالجهاز اللوحي، يلزمك تشغيل بلوتوث أولاً."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"تشغيل"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index 04d680b..a949dbb 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Sürətli Ayarları yenidən tənzimləyin"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Sürətli ayarlarda parlaqlılığı göstərin"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivləşsin?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Tabletinizlə klaviaturaya bağlanmaq üçün ilk olaraq Bluetooth\'u aktivləşdirməlisiniz."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivləşdirin"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index ca45d24..a59f4c5 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Пренареждане на бързите настройки"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Показване на яркостта от бързите настройки"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментални"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се включи ли Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"За да свържете клавиатурата с таблета си, първо трябва да включите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Включване"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index 7df68ac..f4adc14 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"দ্রুত সেটিংসে পুনরায় সাজান"</string>
     <string name="show_brightness" msgid="6613930842805942519">"দ্রুত সেটিংসে উজ্জ্বলতা দেখান"</string>
     <string name="experimental" msgid="6198182315536726162">"পরীক্ষামূলক"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth চালু করবেন?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"আপনার ট্যাবলেটের সাথে আপনার কীবোর্ড সংযুক্ত করতে, আপনাকে প্রথমে Bluetooth চালু করতে হবে।"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"চালু করুন"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index c85fac6..00f96a7 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -439,7 +439,10 @@
     <string name="activity_not_found" msgid="348423244327799974">"L\'aplicació no està instal·lada al dispositiu"</string>
     <string name="clock_seconds" msgid="7689554147579179507">"Mostra els segons del rellotge"</string>
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra els segons del rellotge a la barra d\'estat. Això pot afectar la durada de la bateria."</string>
-    <string name="qs_rearrange" msgid="8060918697551068765">"Torna a ordenar la Configuració ràpida"</string>
-    <string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a la Configuració ràpida"</string>
+    <string name="qs_rearrange" msgid="8060918697551068765">"Reorganitza Configuració ràpida"</string>
+    <string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a Configuració ràpida"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vols activar el Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connectar el teclat amb la tauleta, primer has d\'activar el Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activa"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index d58b11b..e429a06 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -444,4 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Změnit uspořádání Rychlého nastavení"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Zobrazit jas v Rychlém nastavení"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentální"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnout Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Chcete-li klávesnici připojit k tabletu, nejdříve musíte zapnout Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Zapnout"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index f300b3e..e4436c8 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -85,13 +85,13 @@
     <string name="accessibility_search_light" msgid="1103867596330271848">"Søg"</string>
     <string name="accessibility_camera_button" msgid="8064671582820358152">"Kamera"</string>
     <string name="accessibility_phone_button" msgid="6738112589538563574">"Telefon"</string>
-    <string name="accessibility_voice_assist_button" msgid="487611083884852965">"Voice Assist"</string>
+    <string name="accessibility_voice_assist_button" msgid="487611083884852965">"Taleassistent"</string>
     <string name="accessibility_unlock_button" msgid="128158454631118828">"Lås op"</string>
     <string name="accessibility_unlock_button_fingerprint" msgid="8214125623493923751">"Knap til oplåsning. Venter på fingeraftryk"</string>
     <string name="accessibility_unlock_without_fingerprint" msgid="7541705575183694446">"Lås op uden at bruge dit fingeraftryk"</string>
     <string name="unlock_label" msgid="8779712358041029439">"lås op"</string>
     <string name="phone_label" msgid="2320074140205331708">"åbn telefon"</string>
-    <string name="voice_assist_label" msgid="3956854378310019854">"åbn voice assist"</string>
+    <string name="voice_assist_label" msgid="3956854378310019854">"åbn taleassistent"</string>
     <string name="camera_label" msgid="7261107956054836961">"åbn kamera"</string>
     <string name="recents_caption_resize" msgid="3517056471774958200">"Vælg nyt opgavelayout"</string>
     <string name="cancel" msgid="6442560571259935130">"Annuller"</string>
@@ -315,7 +315,7 @@
     <string name="notification_tap_again" msgid="8524949573675922138">"Tryk igen for at åbne"</string>
     <string name="keyguard_unlock" msgid="8043466894212841998">"Stryg opad for at låse op"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Stryg fra telefonikonet"</string>
-    <string name="voice_hint" msgid="8939888732119726665">"Stryg fra ikonet for voice assist"</string>
+    <string name="voice_hint" msgid="8939888732119726665">"Stryg fra mikrofonikonet"</string>
     <string name="camera_hint" msgid="7939688436797157483">"Stryg fra kameraikonet"</string>
     <string name="interruption_level_none_with_warning" msgid="5114872171614161084">"Helt lydløs. Denne handling slukker også skærmlæsere."</string>
     <string name="interruption_level_none" msgid="6000083681244492992">"Total stilhed"</string>
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentel"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå Bluetooth til?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Slå til"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index ac513e4..b113083 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Schnelleinstellungen neu anordnen"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Helligkeit in den Schnelleinstellungen anzeigen"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentell"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivieren?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Zum Verbinden von Tastatur und Tablet muss Bluetooth aktiviert sein."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivieren"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index c8ff7c6..27a019c 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Αναδιάταξη Γρήγορων ρυθμίσεων"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Εμφάνιση φωτεινότητας στις Γρήγορες ρυθμίσεις"</string>
     <string name="experimental" msgid="6198182315536726162">"Σε πειραματικό στάδιο"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Ενεργοποίηση Bluetooth;"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Για να συνδέσετε το πληκτρολόγιο με το tablet σας, θα πρέπει πρώτα να ενεργοποιήσετε το Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ενεργοποίηση"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 6e66842..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Turn on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 6e66842..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Turn on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 6e66842..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Turn on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 72258fa..bca484a 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar la Configuración rápida"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar el brillo en la Configuración rápida"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar el teclado con la tablet, primero debes activar Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 298a0c6..3c89db8 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Ajustes rápidos"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Ajustes rápidos"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para poder conectar tu teclado a tu tablet, debes activar el Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 26b3081..01e5848 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Korralda kiirseaded ümber"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Kuva kiirseadetes heledus"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentaalne"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Kas lülitada Bluetooth sisse?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviatuuri ühendamiseks tahvelarvutiga peate esmalt Bluetoothi sisse lülitama."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Lülita sisse"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index 83aac20..613b44a 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Berrantolatu ezarpen bizkorrak"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Erakutsi distira Ezarpen bizkorretan"</string>
     <string name="experimental" msgid="6198182315536726162">"Esperimentala"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth eginbidea aktibatu nahi duzu?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Teklatua tabletara konektatzeko, Bluetooth eginbidea aktibatu behar duzu."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktibatu"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index c8871c4..9967225 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -384,7 +384,7 @@
     <string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"دستگاه قفل باقی می‌ماند تا زمانی که قفل آن را به صورت دستی باز کنید"</string>
     <string name="hidden_notifications_title" msgid="7139628534207443290">"دریافت سریع‌تر اعلان‌ها"</string>
     <string name="hidden_notifications_text" msgid="2326409389088668981">"قبل از باز کردن قفل آنها را مشاهده کنید"</string>
-    <string name="hidden_notifications_cancel" msgid="3690709735122344913">"خیر، سپاسگزارم"</string>
+    <string name="hidden_notifications_cancel" msgid="3690709735122344913">"نه سپاسگزارم"</string>
     <string name="hidden_notifications_setup" msgid="41079514801976810">"راه‌اندازی"</string>
     <string name="notification_expand_button_text" msgid="1037425494153780718">"مشاهده همه"</string>
     <string name="notification_collapse_button_text" msgid="6883253262134328057">"پنهان کردن همه"</string>
@@ -396,7 +396,7 @@
     <string name="screen_pinning_description" msgid="1346522416878235405">"تا زمانی که پین را بردارید، در نما نگه‌داشته می‌شود. برای برداشتن پین، برگشت و نمای کلی را به صورت هم‌زمان لمس کنید و نگه‌دارید."</string>
     <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"تا زمانی که پین را بردارید، در نما نگه‌داشته می‌شود. برای برداشتن پین، نمای کلی را لمس کنید و نگه‌دارید."</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"متوجه شدم"</string>
-    <string name="screen_pinning_negative" msgid="3741602308343880268">"خیر متشکرم"</string>
+    <string name="screen_pinning_negative" msgid="3741602308343880268">"نه سپاسگزارم"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> مخفی شود؟"</string>
     <string name="quick_settings_reset_confirmation_message" msgid="2235970126803317374">"دفعه بعد که آن را روشن کنید، در تنظیمات نشان داده می‌شود."</string>
     <string name="quick_settings_reset_confirmation_button" msgid="2660339101868367515">"پنهان کردن"</string>
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ترتیب مجدد در تنظیمات سریع"</string>
     <string name="show_brightness" msgid="6613930842805942519">"نمایش روشنایی در تنظیمات سریع"</string>
     <string name="experimental" msgid="6198182315536726162">"آزمایشی"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوتوث روشن شود؟"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"برای مرتبط کردن صفحه‌کلید با رایانه لوحی، ابتدا باید بلوتوث را روشن کنید."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"روشن کردن"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 3154d5c..2d138cb 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Järjestä pika-asetukset uudelleen"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Näytä kirkkaus pika-asetuksissa"</string>
     <string name="experimental" msgid="6198182315536726162">"Kokeellinen"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Otetaanko Bluetooth käyttöön?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Jotta voit yhdistää näppäimistön tablettiisi, sinun on ensin otettava Bluetooth käyttöön."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ota käyttöön"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 669ca9b..979ee35 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser les paramètres rapides"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans les paramètres rapides"</string>
     <string name="experimental" msgid="6198182315536726162">"Fonctions expérimentales"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter votre clavier à votre tablette, vous devez d\'abord activer la connectivité Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activer"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index d42190f..9ab6613 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser la fenêtre de configuration rapide"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans fenêtre de configuration rapide"</string>
     <string name="experimental" msgid="6198182315536726162">"Paramètres expérimentaux"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer le Bluetooth ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter un clavier à votre tablette, vous devez avoir activé le Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activer"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index 3c53983..bc44150 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Configuración rápida"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Configuración rápida"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Queres activar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teu teclado co tablet, primeiro tes que activar o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index 0887e75..ba8c517 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ઝડપી સેટિંગ્સને ફરીથી ગોઠવો"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ઝડપી સેટિંગ્સમાં તેજ બતાવો"</string>
     <string name="experimental" msgid="6198182315536726162">"પ્રાયોગિક"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ચાલુ કરવુ છે?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"તમારા ટેબ્લેટ સાથે કીબોર્ડ કનેક્ટ કરવા માટે, તમારે પહેલાં Bluetooth ચાલુ કરવાની જરૂર પડશે."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ચાલુ કરો"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 11a0d3a..669ce5a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"त्वरित सेटिंग को पुन: व्यवस्थित करें"</string>
     <string name="show_brightness" msgid="6613930842805942519">"त्वरित सेटिंग में स्क्रीन की रोशनी दिखाएं"</string>
     <string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटूथ चालू करें?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"अपने कीबोर्ड को अपने टैबलेट से कनेक्ट करने के लिए, आपको पहले ब्लूटूथ चालू करना होगा."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"चालू करें"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index eec3a6f..e928cbc 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -441,4 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Promijeni raspored Brzih postavki"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Prikaži svjetlinu u Brzim postavkama"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentalno"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite li uključiti Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Da biste povezali tipkovnicu s tabletom, morate uključiti Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Uključi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 2dc9ba0..a347ede 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Gyorsbeállítások átrendezése"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Fényerő megjelenítése a gyorsbeállításokban"</string>
     <string name="experimental" msgid="6198182315536726162">"Kísérleti"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Engedélyezi a Bluetooth-kapcsolatot?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ha a billentyűzetet csatlakoztatni szeretné táblagépéhez, először engedélyeznie kell a Bluetooth-kapcsolatot."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Bekapcsolás"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index a7fdb43..18f6c88 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Վերադասավորել Արագ կարգավորումները"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ցույց տալ պայծառությունն Արագ կարգավորումներում"</string>
     <string name="experimental" msgid="6198182315536726162">"Փորձնական"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Միացնե՞լ Bluetooth-ը:"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ստեղնաշարը ձեր պլանշետին միացնելու համար նախ անհրաժեշտ է միացնել Bluetooth-ը:"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Միացնել"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index a6a68d4..afcac63 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Atur Ulang Setelan Cepat"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Tampilkan kecerahan di Setelan Cepat"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Aktifkan Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menghubungkan keyboard dengan tablet, terlebih dahulu aktifkan Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktifkan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index 83468e5..48d0d1a 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Endurraða flýtistillingum"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Sýna birtustig í flýtistillingum"</string>
     <string name="experimental" msgid="6198182315536726162">"Tilraunastillingar"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Kveikja á Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Til að geta tengt lyklaborðið við spjaldtölvuna þarftu fyrst að kveikja á Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Kveikja"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 4449a0a..dea0330 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Riorganizza Impostazioni rapide"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostra luminosità in Impostazioni rapide"</string>
     <string name="experimental" msgid="6198182315536726162">"Sperimentali"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Attivare il Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connettere la tastiera al tablet, devi prima attivare il Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Attiva"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 31c5126..772a3fa 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"סידור מחדש של הגדרות מהירות"</string>
     <string name="show_brightness" msgid="6613930842805942519">"הצג בהירות בהגדרות מהירות"</string>
     <string name="experimental" msgid="6198182315536726162">"ניסיוניות"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"‏האם להפעיל את ה-Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"‏כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"הפעל"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index d1603ff..6e570e3 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"クイック設定を並べ替え"</string>
     <string name="show_brightness" msgid="6613930842805942519">"クイック設定に明るさ調整バーを表示する"</string>
     <string name="experimental" msgid="6198182315536726162">"試験運用版"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"BluetoothをONにしますか?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"タブレットでキーボードに接続するには、最初にBluetoothをONにする必要があります。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ONにする"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index e5e9003..6f26123 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"სწრაფი პარამეტრების გადაწყობა"</string>
     <string name="show_brightness" msgid="6613930842805942519">"სიკაშკაშის ჩვენება სწრაფ პარამეტრებში"</string>
     <string name="experimental" msgid="6198182315536726162">"ექსპერიმენტული"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"გსურთ Bluetooth-ის ჩართვა?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"კლავიატურის ტაბლეტთან დასაკავშირებლად, ჯერ უნდა ჩართოთ Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ჩართვა"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 946d86b..2668de4 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Жылдам параметрлерді қайта реттеу"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Жылдам параметрлерде жарықтықты көрсету"</string>
     <string name="experimental" msgid="6198182315536726162">"Эксперименттік"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth функциясын қосу керек пе?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Пернетақтаны планшетке қосу үшін алдымен Bluetooth функциясын қосу керек."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Қосу"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 264438b..08d761f 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"រៀបចំការកំណត់រហ័សឡើងវិញ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"បង្ហាញកម្រិតពន្លឺនៅក្នុងការកំណត់រហ័ស"</string>
     <string name="experimental" msgid="6198182315536726162">"ពិសោធន៍"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"បើកប៊្លូធូសឬ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ដើម្បីភ្ជាប់ក្តារចុចរបស់អ្នកជាមួយនឹងថេប្លេតរបស់អ្នក អ្នកត្រូវតែបើកប៊្លូធូសជាមុនសិន។"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"បើក"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 1696076..2cf1697 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳನ್ನು ಮರುಹೊಂದಿಸಿ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳಲ್ಲಿ ಪ್ರಖರತೆಯನ್ನು ತೋರಿಸಿ"</string>
     <string name="experimental" msgid="6198182315536726162">"ಪ್ರಾಯೋಗಿಕ"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡುವುದೇ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಅನ್ನು ಟ್ಯಾಬ್ಲೆಟ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು, ನೀವು ಮೊದಲು ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ಆನ್ ಮಾಡು"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index d053b53..4e8be28 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"빠른 설정 재정렬"</string>
     <string name="show_brightness" msgid="6613930842805942519">"빠른 설정에서 밝기 표시"</string>
     <string name="experimental" msgid="6198182315536726162">"베타"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"블루투스를 켜시겠습니까?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"키보드를 태블릿에 연결하려면 먼저 블루투스를 켜야 합니다."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"사용"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index ec4bec7..fc6897f 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Ыкчам жөндөөлөрдү кайра коюу"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ыкчам жөндөөлөрдөн жарык деңгээлин көрсөтүү"</string>
     <string name="experimental" msgid="6198182315536726162">"Сынамык"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth күйгүзүлсүнбү?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Баскычтобуңузду планшетиңизге туташтыруу үчүн, адегенде Bluetooth\'ту күйгүзүшүңүз керек."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Күйгүзүү"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index e5d784f..6bb2043 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ຈັດ​ວາງ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ​ຄືນ​ໃໝ່"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ສະ​ແດງ​ຄວາມ​ແຈ້ງ​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ"</string>
     <string name="experimental" msgid="6198182315536726162">"ຍັງຢູ່ໃນການທົດລອງ"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ເປີດ​ໃຊ້ Bluetooth ບໍ່?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ເພື່ອ​ເຊື່ອມ​ຕໍ່​ແປ້ນ​ພິມ​ຂອງ​ທ່ານ​ກັບ​ແທັບ​ເລັດ​ຂອງ​ທ່ານ, ກ່ອນ​ອື່ນ​ໝົດ​ທ່ານ​ຕ້ອງ​ເປີດ Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ເປີດ​"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index ca50956..8883cb6 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Pertvarkyti sparčiuosius nustatymus"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Rodyti skaistį sparčiuosiuose nustatymuose"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentinė versija"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Įjungti „Bluetooth“?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Kad galėtumėte prijungti klaviatūrą prie planšetinio kompiuterio, pirmiausia turite įjungti „Bluetooth“."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Įjungti"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a87d918..47ca06a 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -441,4 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Pārkārtot ātros iestatījumus"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Rādīt spilgtumu ātrajos iestatījumos"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentāli"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vai ieslēgt Bluetooth savienojumu?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Lai pievienotu tastatūru planšetdatoram, vispirms ir jāieslēdz Bluetooth savienojums."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ieslēgt"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index 1015018..891ffd8 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Преуредете ги Брзи поставки"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Прикажете ја осветленоста во Брзи поставки"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се вклучи Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"За да ја поврзете тастатурата со таблетот, најпрво треба да вклучите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Вклучи"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index 45ec29a..64d4184 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ദ്രുത ക്രമീകരണം പുനഃസജ്ജീകരിക്കുക"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ദ്രുത ക്രമീകരണത്തിൽ തെളിച്ചം കാണിക്കുക"</string>
     <string name="experimental" msgid="6198182315536726162">"പരീക്ഷണാത്മകം!"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ഓണാക്കണോ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"നിങ്ങളുടെ ടാബ്‌ലെറ്റുമായി കീബോർഡ് കണക്റ്റുചെയ്യുന്നതിന്, ആദ്യം Bluetooth ഓണാക്കേണ്ടതുണ്ട്."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ഓണാക്കുക"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 1051e49..740ee0d 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -438,4 +438,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Түргэн тохиргоог дахин засварлах"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Түргэн тохиргоонд гэрэлтүүлэг харах"</string>
     <string name="experimental" msgid="6198182315536726162">"Туршилтын"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth-г асаах уу?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Компьютерийн гараа таблетад холбохын тулд эхлээд Bluetooth-г асаана уу."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Асаах"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index 66843fc..00d7bc8 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिंग्जची पुनर्रचना करा"</string>
     <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिंग्जमध्‍ये चमक दर्शवा"</string>
     <string name="experimental" msgid="6198182315536726162">"प्रायोगिक"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटुथ सुरू करायचे?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"आपला कीबोर्ड आपल्या टॅब्लेटसह कनेक्ट करण्यासाठी, आपल्याला प्रथम ब्लूटुथ चालू करणे आवश्यक आहे."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"चालू करा"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index c50e214..10865dd 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Susun Semula Tetapan Pantas"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Tunjukkan kecerahan dalam Tetapan Pantas"</string>
     <string name="experimental" msgid="6198182315536726162">"Percubaan"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Hidupkan Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menyambungkan papan kekunci anda dengan tablet, anda perlu menghidupkan Bluetooth terlebih dahulu."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Hidupkan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index f4fd3d1..5cb6087 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"အမြန် ဆက်တင်များကို ပြန်စီစဉ်ရန်"</string>
     <string name="show_brightness" msgid="6613930842805942519">"အမြန် ဆက်တင်များထဲက တောက်ပမှုကို ပြရန်"</string>
     <string name="experimental" msgid="6198182315536726162">"စမ်းသပ်ရေး"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ဘလူးတုသ် ဖွင့်ရမလား။"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ကီးဘုတ်ကို တပ်ဘလက်နှင့် ချိတ်ဆက်ရန်၊ ပမထဦးစွာ ဘလူးတုသ်ကို ဖွင့်ပါ။"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ဖွင့်ပါ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index cae404d..39b404a 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Omorganiser hurtiginnstillingene"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i hurtiginnstillingene"</string>
     <string name="experimental" msgid="6198182315536726162">"På forsøksstadiet"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå på Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"For å koble tastaturet til nettbrettet ditt må du først slå på Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Slå på"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 563bc87..2a312f3 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -380,7 +380,7 @@
     <string name="monitoring_description_app_personal" msgid="484599052118316268">"तपाईँ <xliff:g id="APPLICATION">%1$s</xliff:g> सँग जडित हुनुहुन्छ जसले इ-मेल, अनुप्रयोगहरू र वेबसाइट लगायतका तपाईँको निजी नेटवर्क गतिविधिका अनुगमन गर्न सक्छ।"</string>
     <string name="monitoring_description_app_work" msgid="1754325860918060897">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थापन गरिन्छ। यो <xliff:g id="APPLICATION">%2$s</xliff:g> सँग जोडिएको छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधि अनुगमन गर्न सक्छ।\n\nथप जानकारीको लागि, आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
     <string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थापन गरिन्छ। यो <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> सँग जोडिएको छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधि अनुगमन गर्न सक्छ।\n\nतपाईँ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> सँग पनि जडित हुनुहुन्छ, जसले तपाईँको व्यक्तिगत नेटवर्क गतिविधि अनुगमन गर्न सक्छ।"</string>
-    <string name="monitoring_description_vpn_app_device_owned" msgid="4970443827043261703">"तपाईंको उपकरण <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थित गरिन्छ।\n\nतपाईंको प्रशासकले तपाईँको यन्त्र र त्यसको स्थान जानकारीमार्फत सेटिङहरू,  कर्पोरेट पहुँच, अनुप्रयोगहरू, तपाईँको यन्त्रसँग सम्बद्ध डेटा  र तपाईँको यन्त्रको स्थान जानकारीको अनुगमन र व्यवस्थापन गर्न सक्छ।\n\nतपाईं <xliff:g id="APPLICATION">%2$s</xliff:g> सँग जडित हुनुहुन्छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधिका अनुगमन गर्न सक्छ।\n\nथप जानकारीको लागि तपाईको प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
+    <string name="monitoring_description_vpn_app_device_owned" msgid="4970443827043261703">"तपाईँको उपकरण <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थित गरिन्छ।\n\nतपाईँको प्रशासकले तपाईँको यन्त्र र त्यसको स्थान जानकारीमार्फत सेटिङहरू,  कर्पोरेट पहुँच, अनुप्रयोगहरू, तपाईँको यन्त्रसँग सम्बद्ध डेटा  र तपाईँको यन्त्रको स्थान जानकारीको अनुगमन र व्यवस्थापन गर्न सक्छ।\n\nतपाईँ <xliff:g id="APPLICATION">%2$s</xliff:g> सँग जडित हुनुहुन्छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधिका अनुगमन गर्न सक्छ।\n\nथप जानकारीको लागि तपाईको प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
     <string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"तपाईँले नखोले सम्म उपकरण बन्द रहनेछ"</string>
     <string name="hidden_notifications_title" msgid="7139628534207443290">"छिटो सूचनाहरू प्राप्त गर्नुहोस्"</string>
     <string name="hidden_notifications_text" msgid="2326409389088668981">"तपाईँले अनलक गर्नअघि तिनीहरूलाई हेर्नुहोस्"</string>
@@ -393,12 +393,12 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तार गर्नुहोस्"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त पार्नुहोस्"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"पर्दा राखेका छ"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"तपाईं अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन फिर्ता र सिंहावलोकन नै समय मा पकड।"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"तपाईं अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन गर्न सिंहावलोकन पकड।"</string>
+    <string name="screen_pinning_description" msgid="1346522416878235405">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन फिर्ता र सिंहावलोकन नै समय मा पकड।"</string>
+    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन गर्न सिंहावलोकन पकड।"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"बुझेँ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"धन्यवाद पर्दैन"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"लुकाउनुहुन्छ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
-    <string name="quick_settings_reset_confirmation_message" msgid="2235970126803317374">"यो तपाईं सेटिङ् मा यो बारी अर्को समय देखापर्नेछ।"</string>
+    <string name="quick_settings_reset_confirmation_message" msgid="2235970126803317374">"यो तपाईँ सेटिङ् मा यो बारी अर्को समय देखापर्नेछ।"</string>
     <string name="quick_settings_reset_confirmation_button" msgid="2660339101868367515">"लुकाउनुहोस्"</string>
     <string name="volumeui_prompt_message" msgid="918680947433389110">"<xliff:g id="APP_NAME">%1$s</xliff:g> भोल्यूम संवाद बन्न चाहन्छ।"</string>
     <string name="volumeui_prompt_allow" msgid="7954396902482228786">"अनुमति दिनुहोस्"</string>
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string>
     <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string>
     <string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्नुहुन्छ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"सक्रिय पार्नुहोस्"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index bf9f0f2..6bbb340 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Snelle instellingen opnieuw indelen"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Helderheid weergeven in Snelle instellingen"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimenteel"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth inschakelen?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Als je je toetsenbord wilt verbinden met je tablet, moet je eerst Bluetooth inschakelen."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Inschakelen"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index 45d56cc..61b68c1 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਨੂੰ ਦੁਬਾਰਾ ਕ੍ਰਮ ਦਿਓ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਚਮਕ ਦਿਖਾਓ"</string>
     <string name="experimental" msgid="6198182315536726162">"ਪ੍ਰਯੋਗਾਤਮਿਕ"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ਚਾਲੂ ਕਰੋ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ਆਪਣੇ ਟੈਬਲੇਟ ਨਾਲ ਆਪਣਾ ਕੀ-ਬੋਰਡ ਕਨੈਕਟ ਕਰਨ ਲਈ, ਤੁਹਾਨੂੰ ਪਹਿਲਾਂ Bluetooth ਚਾਲੂ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੈ।"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ਚਾਲੂ ਕਰੋ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 2807c5a..caa3081 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Uporządkuj Szybkie ustawienia"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Pokaż jasność w Szybkich ustawieniach"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperymentalne"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Włączyć Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Aby połączyć klawiaturę z tabletem, musisz najpierw włączyć Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Włącz"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index c8400c9..b82eb32 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ativar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 43088fe9..c137908 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar as Definições rápidas"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar luminosidade nas Definições rápidas"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Pretende ativar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para ligar o teclado ao tablet, tem de ativar primeiro o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ativar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index c8400c9..b82eb32 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ativar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index d64ca7c..b5819ea 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -441,4 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearanjați Setările rapide"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Afișați luminozitatea în Setările rapide"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentale"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Activați Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activați Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activați"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 480de30..0806684 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -444,4 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Изменить порядок Быстрых настроек"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Добавить яркость в Быстрые настройки"</string>
     <string name="experimental" msgid="6198182315536726162">"Экспериментальная функция"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Подключение по Bluetooth"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Чтобы подключить клавиатуру к планшету, включите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Включить"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index e784601..39147ff 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ඉක්මන් සැකසීම් යළි පිළිවෙළට සකසන්න"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ඉක්මන් සැකසීම්වල දීප්තිය පෙන්වන්න"</string>
     <string name="experimental" msgid="6198182315536726162">"පරීක්ෂණාත්මක"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"බ්ලූටූත් ක්‍රියාත්මක කරන්නද?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ඔබේ යතුරු පුවරුව ඔබේ ටැබ්ලට් පරිගණකයට සම්බන්ධ කිරීමට, ඔබ පළමුව බ්ලූටූත් ක්‍රියාත්මක කළ යුතුය."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ක්‍රියාත්මක කරන්න"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 15612ee..86c8fe2 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -444,4 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Zmeniť usporiadanie Rýchlych nastavení"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Zobraziť jas v Rýchlych nastaveniach"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentálne"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnúť Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ak chcete klávesnicu pripojiť k tabletu, najprv musíte zapnúť Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Zapnúť"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 23e4233..c5dd188 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Preuredi hitre nastavitve"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Prikaz svetlosti v hitrih nastavitvah"</string>
     <string name="experimental" msgid="6198182315536726162">"Poskusno"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite vklopiti Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Če želite povezati tipkovnico in tablični računalnik, vklopite Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Vklop"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index 771ad53..fcd65f7 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Risistemo Cilësimet e shpejta"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Shfaq ndriçimin te Cilësimet e shpejta"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentale"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Të aktivizohet \"bluetooth-i\"?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Për të lidhur tastierën me tabletin, në fillim duhet të aktivizosh \"bluetooth-in\"."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivizo"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index f3abc81..2c55a5c 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -441,4 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Преуреди Брза подешавања"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Прикажи осветљеност у Брзим подешавањима"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Желите ли да укључите Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Да бисте повезали тастатуру са таблетом, прво морате да укључите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Укључи"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 3593b34..e8b1765 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -313,10 +313,10 @@
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Mindre brådskande aviseringar nedan"</string>
     <string name="notification_tap_again" msgid="8524949573675922138">"Tryck igen för att öppna"</string>
-    <string name="keyguard_unlock" msgid="8043466894212841998">"Dra uppåt om du vill låsa upp"</string>
-    <string name="phone_hint" msgid="4872890986869209950">"Dra från ikonen och öppna telefonen"</string>
-    <string name="voice_hint" msgid="8939888732119726665">"Dra från ikonen och öppna röstassistenten"</string>
-    <string name="camera_hint" msgid="7939688436797157483">"Dra från ikonen och öppna kameran"</string>
+    <string name="keyguard_unlock" msgid="8043466894212841998">"Svep uppåt om du vill låsa upp"</string>
+    <string name="phone_hint" msgid="4872890986869209950">"Svep från ikonen och öppna telefonen"</string>
+    <string name="voice_hint" msgid="8939888732119726665">"Svep från ikonen och öppna röstassistenten"</string>
+    <string name="camera_hint" msgid="7939688436797157483">"Svep från ikonen och öppna kameran"</string>
     <string name="interruption_level_none_with_warning" msgid="5114872171614161084">"Total tystnad. Även skärmläsningsprogram tystas."</string>
     <string name="interruption_level_none" msgid="6000083681244492992">"Helt tyst"</string>
     <string name="interruption_level_priority" msgid="6426766465363855505">"Bara prioriterade"</string>
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Ordna snabbinställningarna"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Visa ljusstyrka i snabbinställningarna"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentella"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vill du aktivera Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Om du vill ansluta tangentbordet till surfplattan måste du först aktivera Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivera"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index de39e9b..bbe271d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Panga Upya Mipangilio ya Haraka"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Onyesha unga\'avu katika Mipangilio ya Haraka"</string>
     <string name="experimental" msgid="6198182315536726162">"Ya majaribio"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Je, ungependa kuwasha Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ili uunganishe Kibodi yako kwenye kompyuta yako kibao, lazima kwanza uwashe Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Washa"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index a895df7..08bef13 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"விரைவு அமைப்புகளை மறுவரிசைப்படுத்து"</string>
     <string name="show_brightness" msgid="6613930842805942519">"விரைவு அமைப்புகளில் ஒளிர்வுப் பட்டியைக் காட்டு"</string>
     <string name="experimental" msgid="6198182315536726162">"சோதனை முயற்சி"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"புளூடூத்தை இயக்கவா?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"உங்கள் டேப்லெட்டுடன் விசைப்பலகையை இணைக்க, முதலில் புளூடூத்தை இயக்க வேண்டும்."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"இயக்கு"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index c34a33d..46bc704 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"శీఘ్ర సెట్టింగ్‌ల ఏర్పాటు క్రమం మార్చు"</string>
     <string name="show_brightness" msgid="6613930842805942519">"శీఘ్ర సెట్టింగ్‌ల్లో ప్రకాశం చూపు"</string>
     <string name="experimental" msgid="6198182315536726162">"ప్రయోగాత్మకం"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"బ్లూటూత్ ఆన్ చేయాలా?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"మీ కీబోర్డ్‌ను మీ టాబ్లెట్‌తో కనెక్ట్ చేయడానికి, మీరు ముందుగా బ్లూటూత్ ఆన్ చేయాలి."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ఆన్ చేయి"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index d777a0a..f7e7be0 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"จัดเรียงการตั้งค่าด่วนใหม่"</string>
     <string name="show_brightness" msgid="6613930842805942519">"แสดงความสว่างในการตั้งค่าด่วน"</string>
     <string name="experimental" msgid="6198182315536726162">"ทดสอบ"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"เปิดบลูทูธไหม"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"หากต้องการเชื่อมต่อแป้นพิมพ์กับแท็บเล็ต คุณต้องเปิดบลูทูธก่อน"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"เปิด"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 292d3a2..92a6cdc 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Ayusing Muli ang Mga Mabilisang Setting"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ipakita ang liwanag sa Mga Mabilisang Setting"</string>
     <string name="experimental" msgid="6198182315536726162">"Pang-eksperimento"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"I-on ang Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Upang ikonekta ang iyong keyboard sa iyong tablet, kailangan mo munang i-on ang Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"I-on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 9c42e93..2b9d6da 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Hızlı Ayarlar\'ı Yeniden Düzenle"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Hızlı Ayarlar\'da parlaklığı göster"</string>
     <string name="experimental" msgid="6198182315536726162">"Deneysel"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth açılsın mı?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Klavyenizi tabletinize bağlamak için önce Bluetooth\'u açmanız gerekir."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aç"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 9638e80..c040439 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Упорядкувати швидкі налаштування"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Показувати панель яскравості у швидких налаштуваннях"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментальні налаштування"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Увімкнути Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Щоб під’єднати клавіатуру до планшета, спершу потрібно ввімкнути Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Увімкнути"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index 8c2fb64..b0a5336 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"فوری ترتیبات کو دوبارہ ترتیب دیں"</string>
     <string name="show_brightness" msgid="6613930842805942519">"فوری ترتیبات میں چمکیلا پن دکھائیں"</string>
     <string name="experimental" msgid="6198182315536726162">"تجرباتی"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوٹوتھ آن کریں؟"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"اپنے کی بورڈ کو اپنے ٹیبلٹ کے ساتھ منسلک کرنے کیلئے پہلے آپ کو اپنا بلو ٹوتھ آن کرنا ہو گا۔"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"آن کریں"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index 0ad5a69..9b9e2cd 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Tezkor sozlamalarni qayta tartiblash"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Tezkor sozlamalarda yorqinlikni ko‘rsatish"</string>
     <string name="experimental" msgid="6198182315536726162">"Tajribaviy"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth yoqilsinmi?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviaturani planshetingizga ulash uchun Bluetooth xizmatini yoqishingiz kerak."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Yoqish"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index c1aeeb4..d4ed7a5 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Sắp xếp lại Cài đặt nhanh"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Hiển thị độ sáng trong Cài đặt nhanh"</string>
     <string name="experimental" msgid="6198182315536726162">"Thử nghiệm"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bật Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Để kết nối bàn phím với máy tính bảng, trước tiên, bạn phải bật Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Bật"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index fe1c311..05416ca 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速设置"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速设置中显示亮度栏"</string>
     <string name="experimental" msgid="6198182315536726162">"实验性"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"要开启蓝牙吗?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"要将您的键盘连接到平板电脑,您必须先开启蓝牙。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"开启"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index d39adc0..ba4d3cb 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速設定顯示亮度"</string>
     <string name="experimental" msgid="6198182315536726162">"實驗版"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙嗎?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連接至平板電腦,請先開啟藍牙。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"開啟"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6606167..6e0c852 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -442,4 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速設定中顯示亮度"</string>
     <string name="experimental" msgid="6198182315536726162">"實驗性"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙功能嗎?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連線到平板電腦,您必須先開啟藍牙。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"開啟"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 229c16b..147f6b6 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -440,4 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Hlela kabusha izilungiselelo ezisheshayo"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Bonisa ukukhanya kuzilungiselelo ezisheshayo"</string>
     <string name="experimental" msgid="6198182315536726162">"Okokulinga"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vula i-Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ukuze uxhume ikhibhodi yakho nethebhulethi yakho, kufanele uqale ngokuvula i-Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Vula"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index c070a0e..955efb5 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -20,6 +20,7 @@
         <attr name="keyCode" format="integer" />
         <!-- does this button generate longpress / repeat events? -->
         <attr name="keyRepeat" format="boolean" />
+        <attr name="android:contentDescription" />
     </declare-styleable>
     <declare-styleable name="ToggleSlider">
         <attr name="text" format="string" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5661657..cbc92f2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -125,6 +125,7 @@
     <dimen name="pull_span_min">25dp</dimen>
 
     <dimen name="qs_tile_height">88dp</dimen>
+    <dimen name="qs_new_tile_height">100dp</dimen>
     <dimen name="qs_quick_actions_height">88dp</dimen>
     <dimen name="qs_quick_actions_padding">25dp</dimen>
     <dimen name="qs_page_indicator_size">12dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a525fbb..3a5680d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1166,5 +1166,16 @@
     <string name="qs_customize" translatable="false">Allow long-press customize in Quick Settings</string>
     <string name="qs_customize_info" translatable="false">Info</string>
     <string name="qs_customize_remove" translatable="false">Remove</string>
+    <string name="no_tiles_add" translatable="false">No tiles to add</string>
+
+    <!-- Dialog title asking if Bluetooth should be enabled [CHAR LIMIT=NONE] -->
+    <string name="enable_bluetooth_title">Turn on Bluetooth?</string>
+
+    <!-- Dialog message explaining why Bluetooth should be enabled when a packaged keyboard is
+         conncted to the device [CHAR LIMIT=NONE] -->
+    <string name="enable_bluetooth_message">To connect your keyboard with your tablet, you first have to turn on Bluetooth.</string>
+
+    <!-- Bluetooth enablement ok text [CHAR LIMIT=40] -->
+    <string name="enable_bluetooth_confirmation_ok">Turn on</string>
 
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 5bf251b..9551fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -50,6 +50,7 @@
             com.android.systemui.usb.StorageNotification.class,
             com.android.systemui.power.PowerUI.class,
             com.android.systemui.media.RingtonePlayer.class,
+            com.android.systemui.keyboard.KeyboardUI.class,
     };
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java
index a0ceb29..e6c42da 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java
@@ -20,8 +20,8 @@
     public static float evaluate(float value) {
         float evaluation = 0.0f;
         if (value < 1.00) evaluation++;
-        if (value < 0.95) evaluation++;
         if (value < 0.90) evaluation++;
+        if (value < 0.70) evaluation++;
         return evaluation;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
index c125e00..652d969 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
@@ -27,10 +27,12 @@
 
     @Override
     public float getFalseTouchEvaluation(int type, Stroke stroke) {
+        float ratio;
         if (stroke.getTotalLength() == 0.0f) {
-            return 1.0f;
+            ratio = 1.0f;
+        } else {
+            ratio = stroke.getEndPointLength() / stroke.getTotalLength();
         }
-        return EndPointRatioEvaluator.evaluate(
-                stroke.getEndPointLength() / stroke.getTotalLength());
+        return EndPointRatioEvaluator.evaluate(ratio);
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
index a7a5694..27d4c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
@@ -48,7 +48,7 @@
     private final float mDpi;
 
     private HistoryEvaluator mHistoryEvaluator;
-    private boolean mEnableClassifier = true;
+    private boolean mEnableClassifier = false;
     private int mCurrentType = Classifier.GENERIC;
 
     protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java
index 2a45fa3..d50d406 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java
@@ -20,8 +20,8 @@
     public static float evaluate(float value) {
         float evaluation = 0.0f;
         if (value < 1.00) evaluation++;
-        if (value < 0.95) evaluation++;
         if (value < 0.90) evaluation++;
+        if (value < 0.70) evaluation++;
         return evaluation;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java
index c0e4a2d..afd8d01 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java
@@ -19,9 +19,10 @@
 public class SpeedEvaluator {
     public static float evaluate(float value) {
         float evaluation = 0.0f;
-        if (value < 4.0 || value > 35.0) evaluation += 1.0;
-        if (value < 2.2) evaluation += 1.0;
-        if (value > 50.0) evaluation += 1.0;
+        if (value < 4.0) evaluation++;
+        if (value < 2.2) evaluation++;
+        if (value > 35.0) evaluation++;
+        if (value > 50.0) evaluation++;
         return evaluation;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java
index 349aa9e..4c6cea0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java
@@ -19,8 +19,10 @@
 public class SpeedRatioEvaluator {
     public static float evaluate(float value) {
         float evaluation = 0.0f;
-        if (value > 9.0) ++evaluation;
-        if (value > 18.0) ++evaluation;
+        if (value <= 1.0) evaluation++;
+        if (value <= 0.5) evaluation++;
+        if (value > 9.0) evaluation++;
+        if (value > 18.0) evaluation++;
         return evaluation;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java
index 8f9a7e1..48b1b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java
@@ -19,10 +19,10 @@
 public class SpeedVarianceEvaluator {
     public static float evaluate(float value) {
         float evaluation = 0.0f;
-        if (value > 0.06) evaluation += 1.0;
-        if (value > 0.15) evaluation += 1.0;
-        if (value > 0.3) evaluation += 1.0;
-        if (value > 0.6) evaluation += 1.0;
+        if (value > 0.06) evaluation++;
+        if (value > 0.15) evaluation++;
+        if (value > 0.3) evaluation++;
+        if (value > 0.6) evaluation++;
         return evaluation;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
new file mode 100644
index 0000000..64f3e13f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+public class BluetoothDialog extends SystemUIDialog {
+
+    public BluetoothDialog(Context context) {
+        super(context);
+
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+        setShowForAllUsers(true);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
new file mode 100644
index 0000000..96ee397
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.WindowManager;
+
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeChangedListener {
+    private static final String TAG = "KeyboardUI";
+    private static final boolean DEBUG = false;
+
+    // Give BT some time to start after SyUI comes up. This avoids flashing a dialog in the user's
+    // face because BT starts a little bit later in the boot process than SysUI and it takes some
+    // time for us to receive the signal that it's starting.
+    private static final long BLUETOOTH_START_DELAY_MILLIS = 10 * 1000;
+
+    private static final int STATE_NOT_ENABLED = -1;
+    private static final int STATE_UNKNOWN = 0;
+    private static final int STATE_WAITING_FOR_BOOT_COMPLETED = 1;
+    private static final int STATE_WAITING_FOR_TABLET_MODE_EXIT = 2;
+    private static final int STATE_WAITING_FOR_DEVICE_DISCOVERY = 3;
+    private static final int STATE_WAITING_FOR_BLUETOOTH = 4;
+    private static final int STATE_WAITING_FOR_STATE_PAIRED = 5;
+    private static final int STATE_PAIRING = 6;
+    private static final int STATE_PAIRED = 7;
+    private static final int STATE_USER_CANCELLED = 8;
+    private static final int STATE_DEVICE_NOT_FOUND = 9;
+
+    private static final int MSG_INIT = 0;
+    private static final int MSG_ON_BOOT_COMPLETED = 1;
+    private static final int MSG_PROCESS_KEYBOARD_STATE = 2;
+    private static final int MSG_ENABLE_BLUETOOTH = 3;
+    private static final int MSG_ON_BLUETOOTH_STATE_CHANGED = 4;
+    private static final int MSG_ON_DEVICE_BOND_STATE_CHANGED = 5;
+    private static final int MSG_ON_BLUETOOTH_DEVICE_ADDED = 6;
+    private static final int MSG_ON_BLE_SCAN_FAILED = 7;
+    private static final int MSG_SHOW_BLUETOOTH_DIALOG = 8;
+    private static final int MSG_DISMISS_BLUETOOTH_DIALOG = 9;
+
+    private volatile KeyboardHandler mHandler;
+    private volatile KeyboardUIHandler mUIHandler;
+
+    protected volatile Context mContext;
+
+    private boolean mEnabled;
+    private String mKeyboardName;
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private LocalBluetoothAdapter mLocalBluetoothAdapter;
+    private LocalBluetoothProfileManager mProfileManager;
+    private boolean mBootCompleted;
+    private long mBootCompletedTime;
+
+    private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN;
+    private ScanCallback mScanCallback;
+    private BluetoothDialog mDialog;
+
+    private int mState;
+
+    @Override
+    public void start() {
+        mContext = super.mContext;
+        HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+        mHandler = new KeyboardHandler(thread.getLooper());
+        mHandler.sendEmptyMessage(MSG_INIT);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("KeyboardUI:");
+        pw.println("  mEnabled=" + mEnabled);
+        pw.println("  mBootCompleted=" + mEnabled);
+        pw.println("  mBootCompletedTime=" + mBootCompletedTime);
+        pw.println("  mKeyboardName=" + mKeyboardName);
+        pw.println("  mInTabletMode=" + mInTabletMode);
+        pw.println("  mState=" + stateToString(mState));
+    }
+
+    @Override
+    protected void onBootCompleted() {
+        mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED);
+    }
+
+    @Override
+    public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+        if (DEBUG) {
+            Slog.d(TAG, "onTabletModeChanged(" + whenNanos + ", " + inTabletMode + ")");
+        }
+
+        if (inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_ON
+                || !inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_OFF) {
+            mInTabletMode = inTabletMode ?
+                    InputManager.SWITCH_STATE_ON : InputManager.SWITCH_STATE_OFF;
+            processKeyboardState();
+        }
+    }
+
+    // Shoud only be called on the handler thread
+    private void init() {
+        Context context = mContext;
+        mKeyboardName =
+                context.getString(com.android.internal.R.string.config_packagedKeyboardName);
+        if (TextUtils.isEmpty(mKeyboardName)) {
+            if (DEBUG) {
+                Slog.d(TAG, "No packaged keyboard name given.");
+            }
+            return;
+        }
+
+        LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(context, null);
+        if (bluetoothManager == null)  {
+            if (DEBUG) {
+                Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance");
+            }
+            return;
+        }
+        mEnabled = true;
+        mCachedDeviceManager = bluetoothManager.getCachedDeviceManager();
+        mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter();
+        mProfileManager = bluetoothManager.getProfileManager();
+        bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());
+
+        InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+        im.registerOnTabletModeChangedListener(this, mHandler);
+        mInTabletMode = im.isInTabletMode();
+
+        processKeyboardState();
+        mUIHandler = new KeyboardUIHandler();
+    }
+
+    // Should only be called on the handler thread
+    private void processKeyboardState() {
+        mHandler.removeMessages(MSG_PROCESS_KEYBOARD_STATE);
+
+        if (!mEnabled) {
+            mState = STATE_NOT_ENABLED;
+            return;
+        }
+
+        if (!mBootCompleted) {
+            mState = STATE_WAITING_FOR_BOOT_COMPLETED;
+            return;
+        }
+
+        if (mInTabletMode != InputManager.SWITCH_STATE_OFF) {
+            if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) {
+                stopScanning();
+            }
+            mState = STATE_WAITING_FOR_TABLET_MODE_EXIT;
+            return;
+        }
+
+        final int btState = mLocalBluetoothAdapter.getState();
+        if (btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON
+                && mState == STATE_WAITING_FOR_BLUETOOTH) {
+            // If we're waiting for bluetooth but it has come on in the meantime, or is coming
+            // on, just dismiss the dialog. This frequently happens during device startup.
+            mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG);
+        }
+
+        if (btState == BluetoothAdapter.STATE_TURNING_ON) {
+            mState = STATE_WAITING_FOR_BLUETOOTH;
+            // Wait for bluetooth to fully come on.
+            return;
+        }
+
+        if (btState != BluetoothAdapter.STATE_ON) {
+            mState = STATE_WAITING_FOR_BLUETOOTH;
+            showBluetoothDialog();
+            return;
+        }
+
+        CachedBluetoothDevice device = getPairedKeyboard();
+        if (mState == STATE_WAITING_FOR_TABLET_MODE_EXIT || mState == STATE_WAITING_FOR_BLUETOOTH) {
+            if (device != null) {
+                // If we're just coming out of tablet mode or BT just turned on,
+                // then we want to go ahead and automatically connect to the
+                // keyboard. We want to avoid this in other cases because we might
+                // be spuriously called after the user has manually disconnected
+                // the keyboard, meaning we shouldn't try to automtically connect
+                // it again.
+                mState = STATE_PAIRED;
+                device.connect(false);
+                return;
+            }
+            mCachedDeviceManager.clearNonBondedDevices();
+        }
+
+        device = getDiscoveredKeyboard();
+        if (device != null) {
+            mState = STATE_PAIRING;
+            device.startPairing();
+        } else {
+            mState = STATE_WAITING_FOR_DEVICE_DISCOVERY;
+            startScanning();
+        }
+    }
+
+    // Should only be called on the handler thread
+    public void onBootCompletedInternal() {
+        mBootCompleted = true;
+        mBootCompletedTime = SystemClock.uptimeMillis();
+        if (mState == STATE_WAITING_FOR_BOOT_COMPLETED) {
+            processKeyboardState();
+        }
+    }
+
+    // Should only be called on the handler thread
+    private void showBluetoothDialog() {
+        if (isUserSetupComplete()) {
+            long now = SystemClock.uptimeMillis();
+            long earliestDialogTime = mBootCompletedTime + BLUETOOTH_START_DELAY_MILLIS;
+            if (earliestDialogTime < now) {
+                mUIHandler.sendEmptyMessage(MSG_SHOW_BLUETOOTH_DIALOG);
+            } else {
+                mHandler.sendEmptyMessageAtTime(MSG_PROCESS_KEYBOARD_STATE, earliestDialogTime);
+            }
+        } else {
+            // If we're in setup wizard and the keyboard is docked, just automatically enable BT.
+            mLocalBluetoothAdapter.enable();
+        }
+    }
+
+    private boolean isUserSetupComplete() {
+        ContentResolver resolver = mContext.getContentResolver();
+        return Secure.getIntForUser(
+                resolver, Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+    }
+
+    private CachedBluetoothDevice getPairedKeyboard() {
+        Set<BluetoothDevice> devices = mLocalBluetoothAdapter.getBondedDevices();
+        for (BluetoothDevice d : devices) {
+            if (mKeyboardName.equals(d.getName())) {
+                return getCachedBluetoothDevice(d);
+            }
+        }
+        return null;
+    }
+
+    private CachedBluetoothDevice getDiscoveredKeyboard() {
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        for (CachedBluetoothDevice d : devices) {
+            if (d.getName().equals(mKeyboardName)) {
+                return d;
+            }
+        }
+        return null;
+    }
+
+
+    private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice d) {
+        CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(d);
+        if (cachedDevice == null) {
+            cachedDevice = mCachedDeviceManager.addDevice(
+                    mLocalBluetoothAdapter, mProfileManager, d);
+        }
+        return cachedDevice;
+    }
+
+    private void startScanning() {
+        BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner();
+        ScanFilter filter = (new ScanFilter.Builder()).setDeviceName(mKeyboardName).build();
+        ScanSettings settings = (new ScanSettings.Builder())
+            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+            .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
+            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+            .setReportDelay(0)
+            .build();
+        mScanCallback = new KeyboardScanCallback();
+        scanner.startScan(Arrays.asList(filter), settings, mScanCallback);
+    }
+
+    private void stopScanning() {
+        if (mScanCallback != null) {
+            mLocalBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
+            mScanCallback = null;
+        }
+    }
+
+    // Should only be called on the handler thread
+    private void onDeviceAddedInternal(CachedBluetoothDevice d) {
+        if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && d.getName().equals(mKeyboardName)) {
+            stopScanning();
+            d.startPairing();
+            mState = STATE_PAIRING;
+        }
+    }
+
+    // Should only be called on the handler thread
+    private void onBluetoothStateChangedInternal(int bluetoothState) {
+        if (bluetoothState == BluetoothAdapter.STATE_ON && mState == STATE_WAITING_FOR_BLUETOOTH) {
+            processKeyboardState();
+        }
+    }
+
+    // Should only be called on the handler thread
+    private void onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState) {
+        if (d.getName().equals(mKeyboardName) && bondState == BluetoothDevice.BOND_BONDED) {
+            // We don't need to manually connect to the device here because it will automatically
+            // try to connect after it has been paired.
+            mState = STATE_PAIRED;
+        }
+    }
+
+    // Should only be called on the handler thread
+    private void onBleScanFailedInternal() {
+        mScanCallback = null;
+        if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) {
+            mState = STATE_DEVICE_NOT_FOUND;
+        }
+    }
+
+    private final class KeyboardUIHandler extends Handler {
+        public KeyboardUIHandler() {
+            super(Looper.getMainLooper(), null, true /*async*/);
+        }
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case MSG_SHOW_BLUETOOTH_DIALOG: {
+                    DialogInterface.OnClickListener listener = new BluetoothDialogClickListener();
+                    mDialog = new BluetoothDialog(mContext);
+                    mDialog.setTitle(R.string.enable_bluetooth_title);
+                    mDialog.setMessage(R.string.enable_bluetooth_message);
+                    mDialog.setPositiveButton(R.string.enable_bluetooth_confirmation_ok, listener);
+                    mDialog.setNegativeButton(android.R.string.cancel, listener);
+                    mDialog.show();
+                    break;
+                }
+                case MSG_DISMISS_BLUETOOTH_DIALOG: {
+                    if (mDialog != null) {
+                        mDialog.dismiss();
+                        mDialog = null;
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    private final class KeyboardHandler extends Handler {
+        public KeyboardHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case MSG_INIT: {
+                    init();
+                    break;
+                }
+                case MSG_ON_BOOT_COMPLETED: {
+                    onBootCompletedInternal();
+                    break;
+                }
+                case MSG_PROCESS_KEYBOARD_STATE: {
+                    processKeyboardState();
+                    break;
+                }
+                case MSG_ENABLE_BLUETOOTH: {
+                    boolean enable = msg.arg1 == 1;
+                    if (enable) {
+                        mLocalBluetoothAdapter.enable();
+                    } else {
+                        mState = STATE_USER_CANCELLED;
+                    }
+                }
+                case MSG_ON_BLUETOOTH_STATE_CHANGED: {
+                    int bluetoothState = msg.arg1;
+                    onBluetoothStateChangedInternal(bluetoothState);
+                    break;
+                }
+                case MSG_ON_DEVICE_BOND_STATE_CHANGED: {
+                    CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj;
+                    int bondState = msg.arg1;
+                    onDeviceBondStateChangedInternal(d, bondState);
+                    break;
+                }
+                case MSG_ON_BLUETOOTH_DEVICE_ADDED: {
+                    BluetoothDevice d = (BluetoothDevice)msg.obj;
+                    CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d);
+                    onDeviceAddedInternal(cachedDevice);
+                    break;
+
+                }
+                case MSG_ON_BLE_SCAN_FAILED: {
+                    onBleScanFailedInternal();
+                    break;
+                }
+            }
+        }
+    }
+
+    private final class BluetoothDialogClickListener implements DialogInterface.OnClickListener {
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            int enable = DialogInterface.BUTTON_POSITIVE == which ? 1 : 0;
+            mHandler.obtainMessage(MSG_ENABLE_BLUETOOTH, enable, 0).sendToTarget();
+            mDialog = null;
+        }
+    }
+
+    private final class KeyboardScanCallback extends ScanCallback {
+
+        private boolean isDeviceDiscoverable(ScanResult result) {
+            final ScanRecord scanRecord = result.getScanRecord();
+            final int flags = scanRecord.getAdvertiseFlags();
+            final int BT_DISCOVERABLE_MASK = 0x03;
+
+            return (flags & BT_DISCOVERABLE_MASK) != 0;
+        }
+
+        @Override
+        public void onBatchScanResults(List<ScanResult> results) {
+            if (DEBUG) {
+                Slog.d(TAG, "onBatchScanResults(" + results.size() + ")");
+            }
+
+            BluetoothDevice bestDevice = null;
+            int bestRssi = Integer.MIN_VALUE;
+
+            for (ScanResult result : results) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onBatchScanResults: considering " + result);
+                }
+
+                if (isDeviceDiscoverable(result) && result.getRssi() > bestRssi) {
+                    bestDevice = result.getDevice();
+                    bestRssi = result.getRssi();
+                }
+            }
+
+            if (bestDevice != null) {
+                mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, bestDevice).sendToTarget();
+            }
+        }
+
+        @Override
+        public void onScanFailed(int errorCode) {
+            if (DEBUG) {
+                Slog.d(TAG, "onScanFailed(" + errorCode + ")");
+            }
+            mHandler.obtainMessage(MSG_ON_BLE_SCAN_FAILED).sendToTarget();
+        }
+
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            if (DEBUG) {
+                Slog.d(TAG, "onScanResult(" + callbackType + ", " + result + ")");
+            }
+
+            if (isDeviceDiscoverable(result)) {
+                mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED,
+                        result.getDevice()).sendToTarget();
+            } else if (DEBUG) {
+                Slog.d(TAG, "onScanResult: device " + result.getDevice() +
+                       " is not discoverable, ignoring");
+            }
+        }
+    }
+
+    private final class BluetoothCallbackHandler implements BluetoothCallback {
+        @Override
+        public void onBluetoothStateChanged(int bluetoothState) {
+            mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED,
+                    bluetoothState, 0).sendToTarget();
+        }
+
+        @Override
+        public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+            mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED,
+                    bondState, 0, cachedDevice).sendToTarget();
+        }
+
+        @Override
+        public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { }
+        @Override
+        public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { }
+        @Override
+        public void onScanningStateChanged(boolean started) { }
+        @Override
+        public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { }
+    }
+
+    private static String stateToString(int state) {
+        switch (state) {
+            case STATE_NOT_ENABLED:
+                return "STATE_NOT_ENABLED";
+            case STATE_WAITING_FOR_BOOT_COMPLETED:
+                return "STATE_WAITING_FOR_BOOT_COMPLETED";
+            case STATE_WAITING_FOR_TABLET_MODE_EXIT:
+                return "STATE_WAITING_FOR_TABLET_MODE_EXIT";
+            case STATE_WAITING_FOR_DEVICE_DISCOVERY:
+                return "STATE_WAITING_FOR_DEVICE_DISCOVERY";
+            case STATE_WAITING_FOR_BLUETOOTH:
+                return "STATE_WAITING_FOR_BLUETOOTH";
+            case STATE_WAITING_FOR_STATE_PAIRED:
+                return "STATE_WAITING_FOR_STATE_PAIRED";
+            case STATE_PAIRING:
+                return "STATE_PAIRING";
+            case STATE_PAIRED:
+                return "STATE_PAIRED";
+            case STATE_USER_CANCELLED:
+                return "STATE_USER_CANCELLED";
+            case STATE_DEVICE_NOT_FOUND:
+                return "STATE_DEVICE_NOT_FOUND";
+            case STATE_UNKNOWN:
+            default:
+                return "STATE_UNKNOWN";
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 32c906e..0e4a4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -75,8 +75,6 @@
     @Override
     public void setTileVisibility(TileRecord tile, int visibility) {
         tile.tileView.setVisibility(visibility);
-//        // TODO: Do something smarter here.
-//        distributeTiles();
     }
 
     @Override
@@ -104,8 +102,7 @@
         for (int i = 0; i < NT; i++) {
             TileRecord tile = mTiles.get(i);
             if (tile.tile.getTileType() == QSTileView.QS_TYPE_QUICK) {
-                tile.tileView.setType(QSTileView.QS_TYPE_QUICK);
-                mFirstPage.mQuickQuickTiles.addView(tile.tileView);
+                // Don't show any quick tiles for now.
                 continue;
             }
             if (mPages.get(index).isFull()) {
@@ -161,6 +158,7 @@
         protected void onFinishInflate() {
             super.onFinishInflate();
             mQuickQuickTiles = (LinearLayout) findViewById(R.id.quick_tile_layout);
+            mQuickQuickTiles.setVisibility(View.GONE);
             mTilePage = (TilePage) findViewById(R.id.tile_page);
             // Less rows on first page, because it needs room for the quick tiles.
             mTilePage.mMaxRows = 3;
@@ -176,7 +174,7 @@
     }
 
     public static class TilePage extends TileLayout {
-        private int mMaxRows = 4;
+        private int mMaxRows = 3;
 
         public TilePage(Context context, AttributeSet attrs) {
             super(context, attrs);
@@ -188,6 +186,11 @@
             mMaxRows = maxRows;
         }
 
+        @Override
+        protected int getCellHeight() {
+            return mContext.getResources().getDimensionPixelSize(R.dimen.qs_new_tile_height);
+        }
+
         private void clear() {
             if (DEBUG) Log.d(TAG, "Clearing page");
             removeAllViews();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 8e98f10..61cb224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -137,7 +137,7 @@
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
     }
 
-    protected final void refreshState() {
+    public final void refreshState() {
         refreshState(null);
     }
 
@@ -360,6 +360,19 @@
         }
     }
 
+    public static class DrawableIcon extends Icon {
+        protected final Drawable mDrawable;
+
+        public DrawableIcon(Drawable drawable) {
+            mDrawable = drawable;
+        }
+
+        @Override
+        public Drawable getDrawable(Context context) {
+            return mDrawable;
+        }
+    }
+
     public static class ResourceIcon extends Icon {
         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 53d0402..a2d9ef0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -60,7 +60,7 @@
             if (tile.getTileType() == QSTileView.QS_TYPE_QUICK) {
                 quickTiles.add(tile);
             }
-            if (quickTiles.size() == 4) {
+            if (quickTiles.size() == 2) {
                 break;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index b8342e2..1336eec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -67,18 +67,23 @@
     public void updateResources() {
         final Resources res = mContext.getResources();
         final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
-        mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
+        mCellHeight = getCellHeight();
         mCellWidth = (int) (mCellHeight * TILE_ASPECT);
         mLargeCellHeight = mAllowDual ? res.getDimensionPixelSize(R.dimen.qs_dual_tile_height)
                 : mCellHeight;
         mLargeCellWidth = mAllowDual ? (int) (mLargeCellHeight * TILE_ASPECT) : mCellWidth;
-        mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
+        mDualTileUnderlap = mAllowDual
+                ? res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical) : 0;
         if (mColumns != columns) {
             mColumns = columns;
             postInvalidate();
         }
     }
 
+    protected int getCellHeight() {
+        return mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int width = MeasureSpec.getSize(widthMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
index 3491cb6..3f85982 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
@@ -49,7 +49,7 @@
     }
 
     @Override
-    protected QSTile<?> createTile(String tileSpec) {
+    public QSTile<?> createTile(String tileSpec) {
         QSTile<?> tile = super.createTile(tileSpec);
         tile.setTileSpec(tileSpec);
         return tile;
@@ -113,6 +113,11 @@
         return mTiles;
     }
 
+    public void addTile(String spec) {
+        mTiles.add(spec);
+        super.onTuningChanged(TILES_SETTING, null);
+    }
+
     public void replace(String oldTile, String newTile) {
         if (oldTile.equals(newTile)) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
index 0e15f2b..3135408 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
@@ -43,7 +43,7 @@
     }
 
     private void setHovering(boolean hovering) {
-        setAlpha(hovering ? .5f : 1);
+        setAlpha(hovering ? .3f : 1);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
index 012633c..1669278 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
@@ -61,6 +61,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQuickTiles = (QuickTileLayout) findViewById(R.id.quick_tile_layout);
+        mQuickTiles.setVisibility(View.GONE);
         TilePage page = (PagedTileLayout.TilePage) findViewById(R.id.tile_page);
         page.setMaxRows(3 /* First page only gets 3 */);
         mPages.add(page);
@@ -107,12 +108,12 @@
         for (int i = 0; i < NT; i++) {
             TileRecord tile = mTiles.get(i);
             if (tile.tile.getTileType() == QSTileView.QS_TYPE_QUICK) {
-                tile.tileView.setType(QSTileView.QS_TYPE_QUICK);
-                mQuickTiles.addView(tile.tileView);
+                // Ignore quick tiles for now.
                 continue;
             }
             mPages.get(index).addTile(tile);
-            if (mPages.get(index).isFull()) {
+            // Keep everything in one layout for now.
+            if (false && mPages.get(index).isFull()) {
                 if (++index == mPages.size()) {
                     LayoutInflater inflater = LayoutInflater.from(mContext);
                     inflater.inflate(R.layout.horizontal_divider, this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 601961b..b5a885c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -18,18 +18,31 @@
 import android.animation.Animator;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnDismissListener;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.view.*;
+import android.view.ContextThemeWrapper;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
+import android.widget.ListView;
 import android.widget.Toolbar;
 import android.widget.Toolbar.OnMenuItemClickListener;
+
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.qs.QSTile.Host.Callback;
 import com.android.systemui.qs.customize.DropButton.OnDropListener;
+import com.android.systemui.qs.customize.TileAdapter.TileSelectedListener;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -44,7 +57,8 @@
  * *someday* do fancy animations to get into/out of it.
  */
 public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
-        OnDropListener, OnClickListener, Animator.AnimatorListener {
+        OnDropListener, OnClickListener, Animator.AnimatorListener, TileSelectedListener,
+        OnCancelListener, OnDismissListener {
 
     private static final int MENU_SAVE = Menu.FIRST;
     private static final int MENU_RESET = Menu.FIRST + 1;
@@ -61,6 +75,7 @@
     private DropButton mInfoButton;
     private DropButton mRemoveButton;
     private FloatingActionButton mFab;
+    private SystemUIDialog mDialog;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -84,7 +99,7 @@
         TypedValue value = new TypedValue();
         mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
         mToolbar.setNavigationIcon(
-                getResources().getDrawable(value.resourceId, mContext.getTheme()));
+                getResources().getDrawable(R.drawable.ic_close_white, mContext.getTheme()));
         mToolbar.setNavigationOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -162,6 +177,14 @@
     }
 
     @Override
+    public void onTileSelected(String spec) {
+        if (mDialog != null) {
+            mHost.addTile(spec);
+            mDialog.dismiss();
+        }
+    }
+
+    @Override
     public void onTilesChanged() {
         mQsPanel.setTiles(mHost.getTiles());
     }
@@ -193,11 +216,41 @@
     @Override
     public void onClick(View v) {
         if (mFab == v) {
-            // TODO: Show list of tiles.
+            mDialog = new SystemUIDialog(mContext,
+                    android.R.style.Theme_Material_Dialog);
+            View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
+            ListView listView = (ListView) view.findViewById(android.R.id.list);
+            TileAdapter adapter = new TileAdapter(mContext, mHost.getTiles(), mHost);
+            adapter.setListener(this);
+            listView.setDivider(null);
+            listView.setDividerHeight(0);
+            listView.setAdapter(adapter);
+            listView.setEmptyView(view.findViewById(R.id.empty_text));
+            mDialog.setView(view);
+            mDialog.setOnDismissListener(this);
+            mDialog.setOnCancelListener(this);
+            mDialog.show();
+            // Too lazy to figure out what this will be now, but it should probably be something
+            // besides just a dialog.
+            // For now, just make it big.
+            WindowManager.LayoutParams params = mDialog.getWindow().getAttributes();
+            params.width = WindowManager.LayoutParams.MATCH_PARENT;
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            mDialog.getWindow().setAttributes(params);
         }
     }
 
     @Override
+    public void onDismiss(DialogInterface dialog) {
+        mDialog = null;
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        mDialog = null;
+    }
+
+    @Override
     public void onAnimationEnd(Animator animation) {
         if (!isShown) {
             mPhoneStatusBar.getStatusBarWindow().removeView(this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
new file mode 100644
index 0000000..579f58d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.Icon;
+import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.tuner.QSPagingSwitch;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+public class TileAdapter extends BaseAdapter {
+
+    private static final String TAG = "TileAdapter";
+
+    private final ArrayList<TileGroup> mGroups = new ArrayList<>();
+    private final Context mContext;
+
+    private TileSelectedListener mListener;
+    private ArrayList<String> mCurrentTiles;
+
+    public TileAdapter(Context context, Collection<QSTile<?>> currentTiles, QSTileHost host) {
+        mContext = context;
+        addSystemTiles(currentTiles, host);
+        // TODO: Live?
+    }
+
+    private void addSystemTiles(Collection<QSTile<?>> currentTiles, QSTileHost host) {
+        try {
+            ArrayList<String> tileSpecs = new ArrayList<>();
+            for (QSTile<?> tile : currentTiles) {
+                tileSpecs.add(tile.getTileSpec());
+            }
+            mCurrentTiles = tileSpecs;
+            final TileGroup group = new TileGroup("com.android.settings", mContext);
+            // TODO: Pull this list from a more authoritative place.
+            String[] possibleTiles = QSPagingSwitch.QS_PAGE_TILES.split(",");
+            for (int i = 0; i < possibleTiles.length; i++) {
+                final String spec = possibleTiles[i];
+                if (spec.startsWith("q")) {
+                    // Quick tiles can't be customized.
+                    continue;
+                }
+                if (tileSpecs.contains(spec)) {
+                    continue;
+                }
+                final QSTile<?> tile = host.createTile(spec);
+                // Bad, bad, very bad.
+                tile.setListening(true);
+                tile.clearState();
+                tile.refreshState();
+                tile.setListening(false);
+                new Handler(host.getLooper()).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        group.addTile(spec, tile.getState().icon, tile.getState().label, mContext);
+                    }
+                });
+            }
+            // Error: Badness (10000).
+            // Serialize this work after the host's looper's queue is empty.
+            new Handler(host.getLooper()).post(new Runnable() {
+                @Override
+                public void run() {
+                    new Handler(Looper.getMainLooper()).post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (group.mTiles.size() > 0) {
+                                mGroups.add(group);
+                                notifyDataSetChanged();
+                            }
+                            new QueryTilesTask().execute();
+                        }
+                    });
+                }
+            });
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Couldn't load system tiles", e);
+        }
+    }
+
+    public void setListener(TileSelectedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public int getCount() {
+        return mGroups.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mGroups.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return mGroups.get(position).getView(mContext, convertView, parent, mListener);
+    }
+
+    private static class TileGroup {
+        private final ArrayList<TileInfo> mTiles = new ArrayList<>();
+        private CharSequence mLabel;
+        private Drawable mIcon;
+
+        public TileGroup(String pkg, Context context) throws NameNotFoundException {
+            PackageManager pm = context.getPackageManager();
+            ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+            mLabel = info.loadLabel(pm);
+            mIcon = info.loadIcon(pm);
+            Log.d(TAG, "Added " + mLabel);
+        }
+
+        private void addTile(String spec, Drawable icon, String label) {
+            TileInfo info = new TileInfo();
+            info.label = label;
+            info.drawable = icon;
+            info.spec = spec;
+            mTiles.add(info);
+        }
+
+        private void addTile(String spec, Icon icon, String label, Context context) {
+            addTile(spec, icon.getDrawable(context), label);
+        }
+
+        private View getView(Context context, View convertView, ViewGroup parent,
+                final TileSelectedListener listener) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(context).inflate(R.layout.tile_listing, parent,
+                        false);
+            }
+            ((TextView) convertView.findViewById(android.R.id.title)).setText(mLabel);
+            ((ImageView) convertView.findViewById(android.R.id.icon)).setImageDrawable(mIcon);
+            GridLayout grid = (GridLayout) convertView.findViewById(R.id.tile_grid);
+            final int N = mTiles.size();
+            if (grid.getChildCount() != N) {
+                grid.removeAllViews();
+            }
+            for (int i = 0; i < N; i++) {
+                if (grid.getChildCount() <= i) {
+                    grid.addView(createTile(context));
+                }
+                View view = grid.getChildAt(i);
+                final TileInfo tileInfo = mTiles.get(i);
+                ((ImageView) view.findViewById(R.id.tile_icon)).setImageDrawable(tileInfo.drawable);
+                ((TextView) view.findViewById(R.id.tile_label)).setText(tileInfo.label);
+                view.setClickable(true);
+                view.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        listener.onTileSelected(tileInfo.spec);
+                    }
+                });
+            }
+            return convertView;
+        }
+
+        private View createTile(Context context) {
+            return LayoutInflater.from(context).inflate(R.layout.qs_add_tile_layout, null);
+        }
+    }
+
+    private static class TileInfo {
+        private String spec;
+        private Drawable drawable;
+        private String label;
+    }
+
+    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
+        // TODO: Become non-prototype and an API.
+        private static final String TILE_ACTION = "android.intent.action.QS_TILE";
+
+        @Override
+        protected Collection<TileGroup> doInBackground(Void... params) {
+            HashMap<String, TileGroup> pkgMap = new HashMap<>();
+            PackageManager pm = mContext.getPackageManager();
+            // TODO: Handle userness.
+            List<ResolveInfo> services = pm.queryIntentServices(new Intent(TILE_ACTION), 0);
+            for (ResolveInfo info : services) {
+                String packageName = info.serviceInfo.packageName;
+                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+                String spec = CustomTile.PREFIX + componentName.flattenToShortString() + ")";
+                if (mCurrentTiles.contains(spec)) {
+                    continue;
+                }
+                try {
+                    TileGroup group = pkgMap.get(packageName);
+                    if (group == null) {
+                        group = new TileGroup(packageName, mContext);
+                        pkgMap.put(packageName, group);
+                    }
+                    Drawable icon = info.serviceInfo.loadIcon(pm);
+                    CharSequence label = info.serviceInfo.loadLabel(pm);
+                    group.addTile(spec, icon, label != null ? label.toString() : "null");
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Couldn't find resolved package... " + packageName, e);
+                }
+            }
+            return pkgMap.values();
+        }
+
+        @Override
+        protected void onPostExecute(Collection<TileGroup> result) {
+            mGroups.addAll(result);
+            notifyDataSetChanged();
+        }
+    }
+
+    public interface TileSelectedListener {
+        void onTileSelected(String spec);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
new file mode 100644
index 0000000..cf76ed4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+
+public class CustomTile extends QSTile<QSTile.State> {
+    public static final String PREFIX = "custom(";
+
+    private final ComponentName mComponent;
+
+    private CustomTile(Host host, String action) {
+        super(host);
+        mComponent = ComponentName.unflattenFromString(action);
+    }
+
+    public static QSTile<?> create(Host host, String spec) {
+        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+            throw new IllegalArgumentException("Bad intent tile spec: " + spec);
+        }
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty intent tile spec action");
+        }
+        return new CustomTile(host, action);
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+    }
+
+    @Override
+    protected State newTileState() {
+        return new State();
+    }
+
+    @Override
+    protected void handleUserSwitch(int newUserId) {
+        super.handleUserSwitch(newUserId);
+    }
+
+    @Override
+    protected void handleClick() {
+        MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
+    }
+
+    @Override
+    protected void handleLongClick() {
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        // TODO: Actual things.
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+            state.visible = true;
+            state.icon = new DrawableIcon(info.loadIcon(pm));
+            state.label = info.loadLabel(pm).toString();
+            state.contentDescription = state.label;
+        } catch (Exception e) {
+            state.visible = false;
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_INTENT;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
new file mode 100644
index 0000000..79eca30d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+/**
+ * Due to the fact that RecentsActivity is per-user, we need to establish an
+ * interface (this) for the system user to callback to the secondary users in
+ * response to UI events coming in from the system user's SystemUI.
+ */
+oneway interface IRecentsNonSystemUserCallbacks {
+    void preloadRecents();
+    void cancelPreloadingRecents();
+    void showRecents(boolean triggeredFromAltTab);
+    void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
+    void toggleRecents();
+    void onConfigurationChanged();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
new file mode 100644
index 0000000..6b49195
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+/**
+ * Due to the fact that RecentsActivity is per-user, we need to establish an
+ * interface (this) for the non-system user to register itself for callbacks and to
+ * callback to the system user to update internal state.
+ */
+oneway interface IRecentsSystemUserCallbacks {
+    void registerNonSystemUserCallbacks(IBinder nonSystemUserCallbacks, int userId);
+
+    void updateRecentsVisibility(boolean visible);
+    void startScreenPinning();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index aee3623..c216f97 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -16,908 +16,398 @@
 
 package com.android.systemui.recents;
 
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ITaskStackListener;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.os.AsyncTask;
 import android.os.Handler;
-import android.os.SystemClock;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.MutableBoolean;
+import android.util.Log;
 import android.view.Display;
-import android.view.LayoutInflater;
 import android.view.View;
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskGrouping;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
-import com.android.systemui.recents.views.TaskViewHeader;
-import com.android.systemui.recents.views.TaskViewTransform;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 import java.util.ArrayList;
 
-/**
- * Annotation for a method that is only called from the system user's SystemUI process and will be
- * proxied to the current user.
- */
-@interface ProxyFromSystemToCurrentUser {}
-/**
- * Annotation for a method that may be called from any user's SystemUI process and will be proxied
- * to the system user.
- */
-@interface ProxyFromAnyToSystemUser {}
 
-/** A proxy implementation for the recents component */
+/**
+ * An implementation of the SystemUI recents component, which supports both system and secondary
+ * users.
+ */
 public class Recents extends SystemUI
-        implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
+        implements RecentsComponent {
+
+    private final static String TAG = "Recents";
+    private final static boolean DEBUG = false;
 
     public final static int EVENT_BUS_PRIORITY = 1;
+    public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
 
-    final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
-    final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
-    final public static String EXTRA_RECENTS_VISIBILITY = "recentsVisibility";
+    private static SystemServicesProxy sSystemServicesProxy;
+    private static RecentsTaskLoader sTaskLoader;
 
-    // Owner proxy events
-    final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
-            "action_notify_recents_visibility_change";
-    final public static String ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER =
-            "action_screen_pinning_request";
+    private Handler mHandler;
+    private RecentsImpl mImpl;
 
-    final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
-    final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
-    final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
+    // Only For system user, this is the callbacks instance we return to each secondary user
+    private RecentsSystemUser mSystemUserCallbacks;
 
-    final static int sMinToggleDelay = 350;
+    // Only for secondary users, this is the callbacks instance provided by the system user to make
+    // calls back
+    private IRecentsSystemUserCallbacks mCallbacksToSystemUser;
 
-    public final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
-    public final static String sRecentsPackage = "com.android.systemui";
-    public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
+    // The set of runnables to run after binding to the system user's service.
+    private final ArrayList<Runnable> mOnConnectRunnables = new ArrayList<>();
 
-    /**
-     * An implementation of ITaskStackListener, that allows us to listen for changes to the system
-     * task stacks and update recents accordingly.
-     */
-    class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
-        Handler mHandler;
-
-        public TaskStackListenerImpl(Handler handler) {
-            mHandler = handler;
-        }
-
+    // Only for secondary users, this is the death handler for the binder from the system user
+    private final IBinder.DeathRecipient mCallbacksToSystemUserDeathRcpt = new IBinder.DeathRecipient() {
         @Override
-        public void onTaskStackChanged() {
-            // Debounce any task stack changes
-            mHandler.removeCallbacks(this);
-            mHandler.post(this);
-        }
+        public void binderDied() {
+            mCallbacksToSystemUser = null;
 
-        /** Preloads the next task */
-        public void run() {
-            // TODO: Temporarily skip this if multi stack is enabled
-            /*
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
-            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
-                RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-                SystemServicesProxy ssp = loader.getSystemServicesProxy();
-                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
-
-                // Load the next task only if we aren't svelte
-                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(plan, true);
-                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-                // This callback is made when a new activity is launched and the old one is paused
-                // so ignore the current activity and try and preload the thumbnail for the
-                // previous one.
-                if (runningTaskInfo != null) {
-                    launchOpts.runningTaskId = runningTaskInfo.id;
+            // Retry after a fixed duration
+            mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    registerWithSystemUser();
                 }
-                launchOpts.numVisibleTasks = 2;
-                launchOpts.numVisibleTaskThumbnails = 2;
-                launchOpts.onlyLoadForCache = true;
-                launchOpts.onlyLoadPausedActivities = true;
-                loader.loadTasks(mContext, plan, launchOpts);
-            }
-            */
+            }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
         }
-    }
+    };
 
-    /**
-     * A proxy for Recents events which happens strictly for the owner.
-     */
-    class RecentsOwnerEventProxyReceiver extends BroadcastReceiver {
+    // Only for secondary users, this is the service connection we use to connect to the system user
+    private final ServiceConnection mServiceConnectionToSystemUser = new ServiceConnection() {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
-                    visibilityChanged(context,
-                            intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
-                    break;
-                case ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER:
-                    onStartScreenPinning(context);
-                    break;
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (service != null) {
+                mCallbacksToSystemUser = IRecentsSystemUserCallbacks.Stub.asInterface(
+                        service);
+
+                // Listen for system user's death, so that we can reconnect later
+                try {
+                    service.linkToDeath(mCallbacksToSystemUserDeathRcpt, 0);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Lost connection to (System) SystemUI", e);
+                }
+
+                // Run each of the queued runnables
+                runAndFlushOnConnectRunnables();
             }
+
+            // Unbind ourselves now that we've registered our callbacks.  The
+            // binder to the system user are still valid at this point.
+            mContext.unbindService(this);
         }
-    }
 
-    static RecentsTaskLoadPlan sInstanceLoadPlan;
-    static Recents sInstance;
-
-    SystemServicesProxy mSystemServicesProxy;
-    Handler mHandler;
-    TaskStackListenerImpl mTaskStackListener;
-    RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
-    RecentsAppWidgetHost mAppWidgetHost;
-    boolean mBootCompleted;
-    boolean mStartAnimationTriggered;
-    boolean mCanReuseTaskStackViews = true;
-
-    // Task launching
-    RecentsConfiguration mConfig;
-    Rect mSearchBarBounds = new Rect();
-    Rect mTaskStackBounds = new Rect();
-    Rect mLastTaskViewBounds = new Rect();
-    TaskViewTransform mTmpTransform = new TaskViewTransform();
-    int mStatusBarHeight;
-    int mNavBarHeight;
-    int mNavBarWidth;
-    int mTaskBarHeight;
-
-    // Header (for transition)
-    TaskViewHeader mHeaderBar;
-    final Object mHeaderBarLock = new Object();
-    TaskStackView mDummyStackView;
-
-    // Variables to keep track of if we need to start recents after binding
-    boolean mTriggeredFromAltTab;
-    long mLastToggleTime;
-
-    Bitmap mThumbnailTransitionBitmapCache;
-    Task mThumbnailTransitionBitmapCacheKey;
-
-    public Recents() {
-    }
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            // Do nothing
+        }
+    };
 
     /**
-     * Gets the singleton instance and starts it if needed. On the primary user on the device, this
-     * component gets started as a normal {@link SystemUI} component. On a secondary user, this
-     * lifecycle doesn't exist, so we need to start it manually here if needed.
+     * Returns the callbacks interface that non-system users can call.
      */
-    public static Recents getInstanceAndStartIfNeeded(Context ctx) {
-        if (sInstance == null) {
-            sInstance = new Recents();
-            sInstance.mContext = ctx;
-            sInstance.start();
-            sInstance.onBootCompleted();
-        }
-        return sInstance;
+    public IBinder getSystemUserCallbacks() {
+        return mSystemUserCallbacks;
     }
 
-    /** Creates a new broadcast intent */
-    static Intent createLocalBroadcastIntent(Context context, String action) {
-        Intent intent = new Intent(action);
-        intent.setPackage(context.getPackageName());
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
-                Intent.FLAG_RECEIVER_FOREGROUND);
-        return intent;
+    public static RecentsTaskLoader getTaskLoader() {
+        return sTaskLoader;
     }
 
-    /** Initializes the Recents. */
-    @ProxyFromSystemToCurrentUser
+    public static SystemServicesProxy getSystemServices() {
+        return sSystemServicesProxy;
+    }
+
     @Override
     public void start() {
-        if (sInstance == null) {
-            sInstance = this;
-        }
-        Resources res = mContext.getResources();
-        RecentsTaskLoader.initialize(mContext);
-        LayoutInflater inflater = LayoutInflater.from(mContext);
-        mSystemServicesProxy = new SystemServicesProxy(mContext);
+        sSystemServicesProxy = new SystemServicesProxy(mContext);
+        sTaskLoader = new RecentsTaskLoader(mContext);
         mHandler = new Handler();
-        mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
+        mImpl = new RecentsImpl(mContext);
 
-        // Register the task stack listener
-        mTaskStackListener = new TaskStackListenerImpl(mHandler);
-        mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+        // Register with the event bus
+        EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+        EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
 
-        // Only the owner has the callback to update the SysUI visibility flags, so all non-owner
-        // instances of RecentsComponent needs to notify the owner when the visibility
-        // changes.
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
-            filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
-            mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter,
-                    null, mHandler);
+        // Due to the fact that RecentsActivity is per-user, we need to establish and interface for
+        // the system user's Recents component to pass events (like show/hide/toggleRecents) to the
+        // secondary user, and vice versa (like visibility change, screen pinning).
+        final int processUser = sSystemServicesProxy.getProcessUser();
+        if (sSystemServicesProxy.isSystemUser(processUser)) {
+            // For the system user, initialize an instance of the interface that we can pass to the
+            // secondary user
+            mSystemUserCallbacks = new RecentsSystemUser(mContext, mImpl);
+        } else {
+            // For the secondary user, bind to the primary user's service to get a persistent
+            // interface to register its implementation and to later update its state
+            registerWithSystemUser();
         }
-
-        // Initialize the static configuration resources
-        mConfig = RecentsConfiguration.initialize(mContext, mSystemServicesProxy);
-        mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
-        mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
-        mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
-        mDummyStackView = new TaskStackView(mContext, new TaskStack());
-        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
-                null, false);
-        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
-
-        // When we start, preload the data associated with the previous recent tasks.
-        // We can use a new plan since the caches will be the same.
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, true /* isTopTaskHome */);
-        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-        launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
-        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
-        launchOpts.onlyLoadForCache = true;
-        loader.loadTasks(mContext, plan, launchOpts);
         putComponent(Recents.class, this);
     }
 
     @Override
     public void onBootCompleted() {
-        mBootCompleted = true;
-        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+        mImpl.onBootCompleted();
     }
 
-    /** Shows the Recents. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Shows the Recents.
+     */
     @Override
     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            showRecentsInternal(triggeredFromAltTab);
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.showRecents(triggeredFromAltTab);
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.showRecents(triggeredFromAltTab);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
-    void showRecentsInternal(boolean triggeredFromAltTab) {
-        mTriggeredFromAltTab = triggeredFromAltTab;
-
-        try {
-            showRecentsActivity();
-        } catch (ActivityNotFoundException e) {
-            Console.logRawError("Failed to launch RecentAppsIntent", e);
-        }
-    }
-
-    /** Hides the Recents. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Hides the Recents.
+     */
     @Override
     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            hideRecentsInternal(triggeredFromAltTab, triggeredFromHomeKey);
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
-    void hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        if (mBootCompleted) {
-            // Defer to the activity to handle hiding recents, if it handles it, then it must still
-            // be visible
-            Intent intent = createLocalBroadcastIntent(mContext, ACTION_HIDE_RECENTS_ACTIVITY);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-        }
-    }
-
-    /** Toggles the Recents activity. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Toggles the Recents activity.
+     */
     @Override
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            toggleRecentsInternal();
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.toggleRecents();
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.toggleRecents();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
-    void toggleRecentsInternal() {
-        mTriggeredFromAltTab = false;
-
-        try {
-            toggleRecentsActivity();
-        } catch (ActivityNotFoundException e) {
-            Console.logRawError("Failed to launch RecentAppsIntent", e);
-        }
-    }
-
-    /** Preloads info for the Recents activity. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Preloads info for the Recents activity.
+     */
     @Override
     public void preloadRecents() {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            preloadRecentsInternal();
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.preloadRecents();
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-        }
-    }
-
-    void preloadRecentsInternal() {
-        // Preload only the raw task list into a new load plan (which will be consumed by the
-        // RecentsActivity) only if there is a task to animate to.
-        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
-        MutableBoolean topTaskHome = new MutableBoolean(true);
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        sInstanceLoadPlan = loader.createLoadPlan(mContext);
-        if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
-            sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
-            loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
-            TaskStack stack = sInstanceLoadPlan.getTaskStack();
-            if (stack.getTaskCount() > 0) {
-                preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView,
-                        topTaskHome.value);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.preloadRecents();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
             }
         }
     }
 
     @Override
     public void cancelPreloadingRecents() {
-        // Do nothing
-    }
-
-    void showRelativeAffiliatedTask(boolean showNextTask) {
-        // Return early if there is no focused stack
-        int focusedStackId = mSystemServicesProxy.getFocusedStack();
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, true /* isTopTaskHome */);
-        TaskStack focusedStack = plan.getTaskStack();
-
-        // Return early if there are no tasks in the focused stack
-        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
-
-        ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
-        // Return early if there is no running task (can't determine affiliated tasks in this case)
-        if (runningTask == null) return;
-        // Return early if the running task is in the home stack (optimization)
-        if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
-
-        // Find the task in the recents list
-        ArrayList<Task> tasks = focusedStack.getTasks();
-        Task toTask = null;
-        ActivityOptions launchOpts = null;
-        int taskCount = tasks.size();
-        int numAffiliatedTasks = 0;
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.key.id == runningTask.id) {
-                TaskGrouping group = task.group;
-                Task.TaskKey toTaskKey;
-                if (showNextTask) {
-                    toTaskKey = group.getNextTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_next_affiliated_task_target,
-                            R.anim.recents_launch_next_affiliated_task_source);
-                } else {
-                    toTaskKey = group.getPrevTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_prev_affiliated_task_target,
-                            R.anim.recents_launch_prev_affiliated_task_source);
-                }
-                if (toTaskKey != null) {
-                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
-                }
-                numAffiliatedTasks = group.getTaskCount();
-                break;
-            }
-        }
-
-        // Return early if there is no next task
-        if (toTask == null) {
-            if (numAffiliatedTasks > 1) {
-                if (showNextTask) {
-                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_next_affiliated_task_bounce));
-                } else {
-                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_prev_affiliated_task_bounce));
-                }
-            }
-            return;
-        }
-
-        // Keep track of actually launched affiliated tasks
-        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
-
-        // Launch the task
-        if (toTask.isActive) {
-            // Bring an active task to the foreground
-            mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.cancelPreloadingRecents();
         } else {
-            mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
-                    toTask.activityLabel, launchOpts);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.cancelPreloadingRecents();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
     @Override
     public void showNextAffiliatedTask() {
-        // Keep track of when the affiliated task is triggered
-        MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
-        showRelativeAffiliatedTask(true);
+        mImpl.showNextAffiliatedTask();
     }
 
     @Override
     public void showPrevAffiliatedTask() {
-        // Keep track of when the affiliated task is triggered
-        MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
-        showRelativeAffiliatedTask(false);
+        mImpl.showPrevAffiliatedTask();
     }
 
-    /** Updates on configuration change. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Updates on configuration change.
+     */
     public void onConfigurationChanged(Configuration newConfig) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            configurationChanged();
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.onConfigurationChanged();
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_CONFIG_CHANGE_TO_USER);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-        }
-    }
-    void configurationChanged() {
-        // Don't reuse task stack views if the configuration changes
-        mCanReuseTaskStackViews = false;
-        mConfig.updateOnConfigurationChange();
-    }
-
-    /**
-     * Prepares the header bar layout for the next transition, if the task view bounds has changed
-     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
-     *
-     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
-     *                               is not already bound (can be expensive)
-     */
-    void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
-        Rect windowRect = mSystemServicesProxy.getWindowRect();
-
-        // Update the configuration for the current state
-        mConfig.update(mContext, mSystemServicesProxy, mSystemServicesProxy.getWindowRect());
-
-        if (tryAndBindSearchWidget) {
-            // Try and pre-emptively bind the search widget on startup to ensure that we
-            // have the right thumbnail bounds to animate to.
-            // Note: We have to reload the widget id before we get the task stack bounds below
-            if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
-                mConfig.getSearchBarBounds(windowRect,
-                        mStatusBarHeight, mSearchBarBounds);
-            }
-        }
-        Rect systemInsets = new Rect(0, mStatusBarHeight,
-                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
-                (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
-        mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
-                mSearchBarBounds, mTaskStackBounds);
-
-        // Rebind the header bar and draw it for the transition
-        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
-        Rect taskStackBounds = new Rect(mTaskStackBounds);
-        algo.setSystemInsets(systemInsets);
-        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
-        Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
-        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
-            mLastTaskViewBounds.set(taskViewBounds);
-
-            int taskViewWidth = taskViewBounds.width();
-            synchronized (mHeaderBarLock) {
-                mHeaderBar.measure(
-                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
-                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
-                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
-            }
-        }
-    }
-
-    /** Toggles the recents activity */
-    void toggleRecentsActivity() {
-        // If the user has toggled it too quickly, then just eat up the event here (it's better than
-        // showing a janky screenshot).
-        // NOTE: Ideally, the screenshot mechanism would take the window transform into account
-        if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
-            return;
-        }
-
-        // If Recents is the front most activity, then we should just communicate with it directly
-        // to launch the first task or dismiss itself
-        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
-        MutableBoolean isTopTaskHome = new MutableBoolean(true);
-        if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
-            // Notify recents to toggle itself
-            Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-            mLastToggleTime = SystemClock.elapsedRealtime();
-            return;
-        } else {
-            // Otherwise, start the recents activity
-            showRecentsActivity(topTask, isTopTaskHome.value);
-        }
-    }
-
-    /** Shows the recents activity if it is not already running */
-    void showRecentsActivity() {
-        // Check if the top task is in the home stack, and start the recents activity
-        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
-        MutableBoolean isTopTaskHome = new MutableBoolean(true);
-        if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
-            showRecentsActivity(topTask, isTopTaskHome.value);
-        }
-    }
-
-    /**
-     * Creates the activity options for a unknown state->recents transition.
-     */
-    ActivityOptions getUnknownTransitionActivityOptions() {
-        mStartAnimationTriggered = false;
-        return ActivityOptions.makeCustomAnimation(mContext,
-                R.anim.recents_from_unknown_enter,
-                R.anim.recents_from_unknown_exit,
-                mHandler, this);
-    }
-
-    /**
-     * Creates the activity options for a home->recents transition.
-     */
-    ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
-        mStartAnimationTriggered = false;
-        if (fromSearchHome) {
-            return ActivityOptions.makeCustomAnimation(mContext,
-                    R.anim.recents_from_search_launcher_enter,
-                    R.anim.recents_from_search_launcher_exit,
-                    mHandler, this);
-        }
-        return ActivityOptions.makeCustomAnimation(mContext,
-                R.anim.recents_from_launcher_enter,
-                R.anim.recents_from_launcher_exit,
-                mHandler, this);
-    }
-
-    /**
-     * Creates the activity options for an app->recents transition.
-     */
-    ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
-            TaskStack stack, TaskStackView stackView) {
-
-        // Update the destination rect
-        Task toTask = new Task();
-        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                topTask.id, toTask);
-        Rect toTaskRect = toTransform.rect;
-        Bitmap thumbnail;
-        if (mThumbnailTransitionBitmapCacheKey != null
-                && mThumbnailTransitionBitmapCacheKey.key != null
-                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
-            thumbnail = mThumbnailTransitionBitmapCache;
-            mThumbnailTransitionBitmapCacheKey = null;
-            mThumbnailTransitionBitmapCache = null;
-        } else {
-            preloadIcon(topTask);
-            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
-        }
-        if (thumbnail != null) {
-            mStartAnimationTriggered = false;
-            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
-                    toTaskRect.height(), mHandler, this);
-        }
-
-        // If both the screenshot and thumbnail fails, then just fall back to the default transition
-        return getUnknownTransitionActivityOptions();
-    }
-
-    /**
-     * Preloads the icon of a task.
-     */
-    void preloadIcon(ActivityManager.RunningTaskInfo task) {
-
-        // Ensure that we load the running task's icon
-        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-        launchOpts.runningTaskId = task.id;
-        launchOpts.loadThumbnails = false;
-        launchOpts.onlyLoadForCache = true;
-        RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
-    }
-
-    /**
-     * Caches the header thumbnail used for a window animation asynchronously into
-     * {@link #mThumbnailTransitionBitmapCache}.
-     */
-    void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
-            TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) {
-        preloadIcon(topTask);
-
-        // Update the header bar if necessary
-        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
-
-        // Update the destination rect
-        mDummyStackView.updateMinMaxScrollForStack(stack);
-        final Task toTask = new Task();
-        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                topTask.id, toTask);
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... params) {
-                return drawThumbnailTransitionBitmap(toTask, toTransform);
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                mThumbnailTransitionBitmapCache = bitmap;
-                mThumbnailTransitionBitmapCacheKey = toTask;
-            }
-        }.execute();
-    }
-
-    /**
-     * Draws the header of a task used for the window animation into a bitmap.
-     */
-    Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
-        if (toTransform != null && toTask.key != null) {
-            Bitmap thumbnail;
-            synchronized (mHeaderBarLock) {
-                int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
-                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
-                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
-                        Bitmap.Config.ARGB_8888);
-                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
-                    thumbnail.eraseColor(0xFFff0000);
-                } else {
-                    Canvas c = new Canvas(thumbnail);
-                    c.scale(toTransform.scale, toTransform.scale);
-                    mHeaderBar.rebindToTask(toTask);
-                    mHeaderBar.draw(c);
-                    c.setBitmap(null);
-                }
-            }
-            return thumbnail.createAshmemBitmap();
-        }
-        return null;
-    }
-
-    /** Returns the transition rect for the given task id. */
-    TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
-            int runningTaskId, Task runningTaskOut) {
-        // Find the running task in the TaskStack
-        Task task = null;
-        ArrayList<Task> tasks = stack.getTasks();
-        if (runningTaskId != -1) {
-            // Otherwise, try and find the task with the
-            int taskCount = tasks.size();
-            for (int i = taskCount - 1; i >= 0; i--) {
-                Task t = tasks.get(i);
-                if (t.key.id == runningTaskId) {
-                    task = t;
-                    runningTaskOut.copyFrom(t);
-                    break;
-                }
-            }
-        }
-        if (task == null) {
-            // If no task is specified or we can not find the task just use the front most one
-            task = tasks.get(tasks.size() - 1);
-            runningTaskOut.copyFrom(task);
-        }
-
-        // Get the transform for the running task
-        stackView.getScroller().setStackScrollToInitialState();
-        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
-                stackView.getScroller().getStackScroll(), mTmpTransform, null);
-        return mTmpTransform;
-    }
-
-    /** Shows the recents activity */
-    void showRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-
-        // Update the header bar if necessary
-        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
-
-        if (sInstanceLoadPlan == null) {
-            // Create a new load plan if onPreloadRecents() was never triggered
-            sInstanceLoadPlan = loader.createLoadPlan(mContext);
-        }
-
-        if (!sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
-        }
-        TaskStack stack = sInstanceLoadPlan.getTaskStack();
-
-        // Prepare the dummy stack for the transition
-        mDummyStackView.updateMinMaxScrollForStack(stack);
-        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
-                mDummyStackView.computeStackVisibilityReport();
-        boolean hasRecentTasks = stack.getTaskCount() > 0;
-        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
-
-        if (useThumbnailTransition) {
-            // Try starting with a thumbnail transition
-            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
-                    mDummyStackView);
-            if (opts != null) {
-                startRecentsActivity(topTask, opts, false /* fromHome */,
-                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
-            } else {
-                // Fall through below to the non-thumbnail transition
-                useThumbnailTransition = false;
-            }
-        }
-
-        if (!useThumbnailTransition) {
-            // If there is no thumbnail transition, but is launching from home into recents, then
-            // use a quick home transition and do the animation from home
-            if (hasRecentTasks) {
-                String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
-                String searchWidgetPackage =
-                        Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
-
-                // Determine whether we are coming from a search owned home activity
-                boolean fromSearchHome = (homeActivityPackage != null) &&
-                        homeActivityPackage.equals(searchWidgetPackage);
-                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
-                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
-                        false /* fromThumbnail */, stackVr);
-            } else {
-                // Otherwise we do the normal fade from an unknown source
-                ActivityOptions opts = getUnknownTransitionActivityOptions();
-                startRecentsActivity(topTask, opts, true /* fromHome */,
-                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
-            }
-        }
-        mLastToggleTime = SystemClock.elapsedRealtime();
-    }
-
-    /** Starts the recents activity */
-    void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
-              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
-              TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
-        // Update the configuration based on the launch options
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        launchState.launchedFromHome = fromSearchHome || fromHome;
-        launchState.launchedFromSearchHome = fromSearchHome;
-        launchState.launchedFromAppWithThumbnail = fromThumbnail;
-        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
-        launchState.launchedWithAltTab = mTriggeredFromAltTab;
-        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
-        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
-        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
-        launchState.launchedHasConfigurationChanged = false;
-
-        Intent intent = new Intent(sToggleRecentsAction);
-        intent.setClassName(sRecentsPackage, sRecentsActivity);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-        if (opts != null) {
-            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
-        } else {
-            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
-        }
-        mCanReuseTaskStackViews = true;
-    }
-
-    /** Notifies the callbacks that the visibility of Recents has changed. */
-    @ProxyFromAnyToSystemUser
-    public static void notifyVisibilityChanged(Context context, SystemServicesProxy ssp,
-            boolean visible) {
-        if (ssp.isForegroundUserSystem()) {
-            visibilityChanged(context, visible);
-        } else {
-            Intent intent = createLocalBroadcastIntent(context,
-                    ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
-            intent.putExtra(EXTRA_RECENTS_VISIBILITY, visible);
-            context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-        }
-    }
-    static void visibilityChanged(Context context, boolean visible) {
-        // For the primary user, the context for the SystemUI component is the SystemUIApplication
-        SystemUIApplication app = (SystemUIApplication)
-                getInstanceAndStartIfNeeded(context.getApplicationContext()).mContext;
-        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
-        if (statusBar != null) {
-            statusBar.updateRecentsVisibility(visible);
-        }
-    }
-
-    /** Notifies the status bar to trigger screen pinning. */
-    @ProxyFromAnyToSystemUser
-    public static void startScreenPinning(Context context, SystemServicesProxy ssp) {
-        if (ssp.isForegroundUserSystem()) {
-            onStartScreenPinning(context);
-        } else {
-            Intent intent = createLocalBroadcastIntent(context,
-                    ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
-            context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-        }
-    }
-    static void onStartScreenPinning(Context context) {
-        // For the primary user, the context for the SystemUI component is the SystemUIApplication
-        SystemUIApplication app = (SystemUIApplication)
-                getInstanceAndStartIfNeeded(context.getApplicationContext()).mContext;
-        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
-        if (statusBar != null) {
-            statusBar.showScreenPinningRequest(false);
-        }
-    }
-
-    /**
-     * Returns the preloaded load plan and invalidates it.
-     */
-    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
-        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
-        sInstanceLoadPlan = null;
-        return plan;
-    }
-
-    /**** OnAnimationStartedListener Implementation ****/
-
-    @Override
-    public void onAnimationStarted() {
-        // Notify recents to start the enter animation
-        if (!mStartAnimationTriggered) {
-            // There can be a race condition between the start animation callback and
-            // the start of the new activity (where we register the receiver that listens
-            // to this broadcast, so we add our own receiver and if that gets called, then
-            // we know the activity has not yet started and we can retry sending the broadcast.
-            BroadcastReceiver fallbackReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (getResultCode() == Activity.RESULT_OK) {
-                        mStartAnimationTriggered = true;
-                        return;
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.onConfigurationChanged();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
                     }
-
-                    // Schedule for the broadcast to be sent again after some time
-                    mHandler.postDelayed(new Runnable() {
-                        @Override
-                        public void run() {
-                            onAnimationStarted();
-                        }
-                    }, 25);
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
                 }
-            };
-
-            // Send the broadcast to notify Recents that the animation has started
-            Intent intent = createLocalBroadcastIntent(mContext, ACTION_START_ENTER_ANIMATION);
-            mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
-                    fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
+            }
         }
     }
+
+    /**
+     * Handle Recents activity visibility changed.
+     */
+    public final void onBusEvent(final RecentsVisibilityChangedEvent event) {
+        int processUser = event.systemServicesProxy.getProcessUser();
+        if (event.systemServicesProxy.isSystemUser(processUser)) {
+            mImpl.onVisibilityChanged(event.applicationContext, event.visible);
+        } else {
+            postToSystemUser(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mCallbacksToSystemUser.updateRecentsVisibility(event.visible);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Handle screen pinning request.
+     */
+    public final void onBusEvent(final ScreenPinningRequestEvent event) {
+        int processUser = event.systemServicesProxy.getProcessUser();
+        if (event.systemServicesProxy.isSystemUser(processUser)) {
+            mImpl.onStartScreenPinning(event.applicationContext);
+        } else {
+            postToSystemUser(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mCallbacksToSystemUser.startScreenPinning();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Attempts to register with the system user.
+     */
+    private void registerWithSystemUser() {
+        final int processUser = sSystemServicesProxy.getProcessUser();
+        postToSystemUser(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mCallbacksToSystemUser.registerNonSystemUserCallbacks(mImpl, processUser);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to register", e);
+                }
+            }
+        });
+    }
+
+    /**
+     * Runs the runnable in the system user's Recents context, connecting to the service if
+     * necessary.
+     */
+    private void postToSystemUser(final Runnable onConnectRunnable) {
+        mOnConnectRunnables.add(onConnectRunnable);
+        if (mCallbacksToSystemUser == null) {
+            Intent systemUserServiceIntent = new Intent();
+            systemUserServiceIntent.setClass(mContext, RecentsSystemUserService.class);
+            boolean bound = mContext.bindServiceAsUser(systemUserServiceIntent,
+                    mServiceConnectionToSystemUser, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+            if (!bound) {
+                // Retry after a fixed duration
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        registerWithSystemUser();
+                    }
+                }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
+            }
+        } else {
+            runAndFlushOnConnectRunnables();
+        }
+    }
+
+    /**
+     * Runs all the queued runnables after a service connection is made.
+     */
+    private void runAndFlushOnConnectRunnables() {
+        for (Runnable r : mOnConnectRunnables) {
+            r.run();
+        }
+        mOnConnectRunnables.clear();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index e647c1f..331a124 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -39,9 +39,15 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.ResizeTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
+import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.Console;
@@ -68,6 +74,7 @@
     RecentsConfiguration mConfig;
     RecentsPackageMonitor mPackageMonitor;
     long mLastTabKeyEventTime;
+    boolean mFinishedOnStartup;
 
     // Top level views
     RecentsView mRecentsView;
@@ -121,36 +128,6 @@
     }
 
     /**
-     * Broadcast receiver to handle messages from AlternateRecentsComponent.
-     */
-    final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Recents.ACTION_HIDE_RECENTS_ACTIVITY)) {
-                if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
-                    // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
-                    dismissRecentsToFocusedTaskOrHome(false);
-                } else if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
-                    // Otherwise, dismiss Recents to Home
-                    dismissRecentsToHome(true);
-                } else {
-                    // Do nothing
-                }
-            } else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
-                // If we are toggling Recents, then first unfilter any filtered stacks first
-                dismissRecentsToFocusedTaskOrHome(true);
-            } else if (action.equals(Recents.ACTION_START_ENTER_ANIMATION)) {
-                // Trigger the enter animation
-                onEnterAnimationTriggered();
-                // Notify the fallback receiver that we have successfully got the broadcast
-                // See AlternateRecentsComponent.onAnimationStarted()
-                setResultCode(Activity.RESULT_OK);
-            }
-        }
-    };
-
-    /**
      * Broadcast receiver to handle messages from the system
      */
     final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() {
@@ -162,7 +139,7 @@
                 dismissRecentsToHomeIfVisible(false);
             } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
                 // When the search activity changes, update the search widget view
-                SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+                SystemServicesProxy ssp = Recents.getSystemServices();
                 mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(context, mAppWidgetHost);
                 refreshSearchWidgetView();
             }
@@ -173,8 +150,8 @@
     void updateRecentsTasks() {
         // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
         // reconstructing the task stack
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        RecentsTaskLoadPlan plan = Recents.consumeInstanceLoadPlan();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
         if (plan == null) {
             plan = loader.createLoadPlan(this);
         }
@@ -266,7 +243,7 @@
     /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
     boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
         RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // If we currently have filtered stacks, then unfilter those first
             if (checkFilteredStackState &&
@@ -296,7 +273,6 @@
                     null, mFinishLaunchHomeRunnable, null);
             mRecentsView.startExitToHomeAnimation(
                     new ViewAnimation.TaskViewExitContext(exitTrigger));
-            mScrimViews.startExitRecentsAnimation();
         } else {
             mFinishLaunchHomeRunnable.run();
         }
@@ -310,7 +286,7 @@
 
     /** Dismisses Recents directly to Home if we currently aren't transitioning. */
     boolean dismissRecentsToHomeIfVisible(boolean animated) {
-        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // Return to Home
             dismissRecentsToHome(animated);
@@ -323,20 +299,25 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mFinishedOnStartup = false;
+
+        // In the case that the activity starts up before the Recents component has initialized
+        // (usually when debugging/pushing the SysUI apk), just finish this activity.
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp == null) {
+            mFinishedOnStartup = true;
+            finish();
+            return;
+        }
 
         // Register this activity with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
-        // For the non-primary user, ensure that the SystemServicesProxy and configuration is
-        // initialized
-        RecentsTaskLoader.initialize(this);
-        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
-        mConfig = RecentsConfiguration.initialize(this, ssp);
-        mConfig.update(this, ssp, ssp.getWindowRect());
-        mPackageMonitor = new RecentsPackageMonitor();
-
         // Initialize the widget host (the host id is static and does not change)
+        mConfig = RecentsConfiguration.getInstance();
         mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
+        mPackageMonitor = new RecentsPackageMonitor();
+        mPackageMonitor.register(this);
 
         // Set the Recents layout
         setContentView(R.layout.recents);
@@ -367,21 +348,6 @@
     @Override
     protected void onStart() {
         super.onStart();
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        MetricsLogger.visible(this, MetricsLogger.OVERVIEW_ACTIVITY);
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        SystemServicesProxy ssp = loader.getSystemServicesProxy();
-        Recents.notifyVisibilityChanged(this, ssp, true);
-
-        // Register the broadcast receiver to handle messages from our service
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY);
-        filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY);
-        filter.addAction(Recents.ACTION_START_ENTER_ANIMATION);
-        registerReceiver(mServiceBroadcastReceiver, filter);
-
-        // Register any broadcast receivers for the task loader
-        mPackageMonitor.register(this);
 
         // Update the recent tasks
         updateRecentsTasks();
@@ -389,15 +355,22 @@
         // If this is a new instance from a configuration change, then we have to manually trigger
         // the enter animation state, or if recents was relaunched by AM, without going through
         // the normal mechanisms
+        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
                 !launchState.launchedFromAppWithThumbnail;
         if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
-            onEnterAnimationTriggered();
+            EventBus.getDefault().send(new EnterRecentsWindowAnimationStartedEvent());
         }
 
         if (!launchState.launchedHasConfigurationChanged) {
             mRecentsView.disableLayersForOneFrame();
         }
+
+        // Notify that recents is now visible
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
+
+        MetricsLogger.visible(this, MetricsLogger.OVERVIEW_ACTIVITY);
     }
 
     @Override
@@ -412,19 +385,10 @@
     @Override
     protected void onStop() {
         super.onStop();
-        MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        SystemServicesProxy ssp = loader.getSystemServicesProxy();
-        Recents.notifyVisibilityChanged(this, ssp, false);
 
-        // Notify the views that we are no longer visible
-        mRecentsView.onRecentsHidden();
-
-        // Unregister the RecentsService receiver
-        unregisterReceiver(mServiceBroadcastReceiver);
-
-        // Unregister any broadcast receivers for the task loader
-        mPackageMonitor.unregister();
+        // Notify that recents is now hidden
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
 
         // Workaround for b/22542869, if the RecentsActivity is started again, but without going
         // through SystemUI, we need to reset the config launch flags to ensure that we do not
@@ -436,45 +400,45 @@
         launchState.launchedToTaskId = -1;
         launchState.launchedWithAltTab = false;
         launchState.launchedHasConfigurationChanged = false;
+
+        MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
 
+        // In the case that the activity finished on startup, just skip the unregistration below
+        if (mFinishedOnStartup) {
+            return;
+        }
+
         // Unregister the system broadcast receivers
         unregisterReceiver(mSystemBroadcastReceiver);
 
+        // Unregister any broadcast receivers for the task loader
+        mPackageMonitor.unregister();
+
         // Stop listening for widget package changes if there was one bound
         mAppWidgetHost.stopListening();
         EventBus.getDefault().unregister(this);
     }
 
-    public void onEnterAnimationTriggered() {
-        // Try and start the enter animation (or restart it on configuration changed)
-        ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
-        ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
-        mRecentsView.startEnterRecentsAnimation(ctx);
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        EventBus.getDefault().register(mScrimViews, EVENT_BUS_PRIORITY);
+    }
 
-        if (mSearchWidgetInfo != null) {
-            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    // Start listening for widget package changes if there is one bound
-                    if (mAppWidgetHost != null) {
-                        mAppWidgetHost.startListening();
-                    }
-                }
-            });
-        }
-
-        // Animate the SystemUI scrim views
-        mScrimViews.startEnterRecentsAnimation();
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        EventBus.getDefault().unregister(mScrimViews);
     }
 
     @Override
     public void onTrimMemory(int level) {
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
         if (loader != null) {
             loader.onTrimMemory(level);
         }
@@ -519,7 +483,7 @@
 
     @Override
     public void onUserInteraction() {
-        mRecentsView.onUserInteraction();
+        EventBus.getDefault().send(new UserInteractionEvent());
     }
 
     @Override
@@ -540,12 +504,6 @@
     /**** RecentsView.RecentsViewCallbacks Implementation ****/
 
     @Override
-    public void onExitToHomeAnimationTriggered() {
-        // Animate the SystemUI scrim views out
-        mScrimViews.startExitRecentsAnimation();
-    }
-
-    @Override
     public void onTaskViewClicked() {
     }
 
@@ -561,30 +519,54 @@
     }
 
     @Override
-    public void onScreenPinningRequest() {
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        SystemServicesProxy ssp = loader.getSystemServicesProxy();
-        Recents.startScreenPinning(this, ssp);
-
-        MetricsLogger.count(this, "overview_screen_pinned", 1);
-    }
-
-    @Override
     public void runAfterPause(Runnable r) {
         mAfterPauseRunnable = r;
     }
 
     /**** EventBus events ****/
 
+    public final void onBusEvent(ToggleRecentsEvent event) {
+        dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
+    }
+
+    public final void onBusEvent(HideRecentsEvent event) {
+        if (event.triggeredFromAltTab) {
+            // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
+            dismissRecentsToFocusedTaskOrHome(false /* checkFilteredStackState */);
+        } else if (event.triggeredFromHomeKey) {
+            // Otherwise, dismiss Recents to Home
+            dismissRecentsToHome(true /* checkFilteredStackState */);
+        } else {
+            // Do nothing
+        }
+    }
+
+    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
+        // Try and start the enter animation (or restart it on configuration changed)
+        ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
+        ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
+        mRecentsView.startEnterRecentsAnimation(ctx);
+        if (mSearchWidgetInfo != null) {
+            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+                @Override
+                public void run() {
+                    // Start listening for widget package changes if there is one bound
+                    if (mAppWidgetHost != null) {
+                        mAppWidgetHost.startListening();
+                    }
+                }
+            });
+        }
+    }
+
     public final void onBusEvent(AppWidgetProviderChangedEvent event) {
         refreshSearchWidgetView();
     }
 
     public final void onBusEvent(ShowApplicationInfoEvent event) {
         // Create a new task stack with the application info details activity
-        Intent baseIntent = event.task.key.baseIntent;
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
-                Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
+                Uri.fromParts("package", event.task.key.getComponent().getPackageName(), null));
         intent.setComponent(intent.resolveActivity(getPackageManager()));
         TaskStackBuilder.create(this)
                 .addNextIntentWithParentStack(intent).startActivities(null,
@@ -596,11 +578,12 @@
 
     public final void onBusEvent(DismissTaskEvent event) {
         // Remove any stored data from the loader
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
         loader.deleteTaskData(event.task, false);
 
         // Remove the task from activity manager
-        loader.getSystemServicesProxy().removeTask(event.task.key.id);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.removeTask(event.task.key.id);
     }
 
     public final void onBusEvent(ResizeTaskEvent event) {
@@ -619,9 +602,13 @@
         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
     }
 
+    public final void onBusEvent(ScreenPinningRequestEvent event) {
+        MetricsLogger.count(this, "overview_screen_pinned", 1);
+    }
+
     private void refreshSearchWidgetView() {
         if (mSearchWidgetInfo != null) {
-            SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+            SystemServicesProxy ssp = Recents.getSystemServices();
             int searchWidgetId = ssp.getSearchAppWidgetId(this);
             mSearchWidgetHostView = (RecentsAppWidgetHostView) mAppWidgetHost.createView(
                     this, searchWidgetId, mSearchWidgetInfo);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 37439e7..563956b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -22,7 +22,6 @@
 import android.provider.Settings;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 
 /**
  * Application resources that can be retrieved from the application context and are not specifically
@@ -112,8 +111,7 @@
     void update(Context context, SystemServicesProxy ssp, Rect windowRect) {
         // Only update resources that can change after the first load, either through developer
         // settings or via multi window
-        lockToAppEnabled = ssp.getSystemSetting(context,
-                Settings.System.LOCK_TO_APP_ENABLED) != 0;
+        lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
         hasDockedTasks = ssp.hasDockedTask();
 
         // Recompute some values based on the given state, since we can not rely on the resource
@@ -144,8 +142,10 @@
         return mLaunchState;
     }
 
-    /** Called when the configuration has changed, and we want to reset any configuration specific
-     * members. */
+    /**
+     * Called when the configuration has changed, and we want to reset any configuration specific
+     * members.
+     */
     public void updateOnConfigurationChange() {
         mLaunchState.updateOnConfigurationChange();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
new file mode 100644
index 0000000..aaeb10c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ITaskStackListener;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.MutableBoolean;
+import android.view.LayoutInflater;
+import android.view.View;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.ForegroundThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskGrouping;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskViewHeader;
+import com.android.systemui.recents.views.TaskViewTransform;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+import java.util.ArrayList;
+
+/**
+ * An implementation of the Recents component for the current user.  For secondary users, this can
+ * be called remotely from the system user.
+ */
+public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
+        implements ActivityOptions.OnAnimationStartedListener {
+
+    private final static String TAG = "RecentsImpl";
+    private final static boolean DEBUG = false;
+
+    private final static int sMinToggleDelay = 350;
+
+    public final static String RECENTS_PACKAGE = "com.android.systemui";
+    public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
+
+
+    /**
+     * An implementation of ITaskStackListener, that allows us to listen for changes to the system
+     * task stacks and update recents accordingly.
+     */
+    class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
+        Handler mHandler;
+
+        public TaskStackListenerImpl(Handler handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void onTaskStackChanged() {
+            // Debounce any task stack changes
+            mHandler.removeCallbacks(this);
+            mHandler.post(this);
+        }
+
+        /** Preloads the next task */
+        public void run() {
+            // TODO: Temporarily skip this if multi stack is enabled
+            /*
+            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+                RecentsTaskLoader loader = Recents.getTaskLoader();
+                SystemServicesProxy ssp = Recents.getSystemServices();
+                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
+
+                // Load the next task only if we aren't svelte
+                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+                loader.preloadTasks(plan, true);
+                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+                // This callback is made when a new activity is launched and the old one is paused
+                // so ignore the current activity and try and preload the thumbnail for the
+                // previous one.
+                if (runningTaskInfo != null) {
+                    launchOpts.runningTaskId = runningTaskInfo.id;
+                }
+                launchOpts.numVisibleTasks = 2;
+                launchOpts.numVisibleTaskThumbnails = 2;
+                launchOpts.onlyLoadForCache = true;
+                launchOpts.onlyLoadPausedActivities = true;
+                loader.loadTasks(mContext, plan, launchOpts);
+            }
+            */
+        }
+    }
+
+    private static RecentsTaskLoadPlan sInstanceLoadPlan;
+
+    Context mContext;
+    Handler mHandler;
+    TaskStackListenerImpl mTaskStackListener;
+    RecentsAppWidgetHost mAppWidgetHost;
+    boolean mBootCompleted;
+    boolean mStartAnimationTriggered;
+    boolean mCanReuseTaskStackViews = true;
+
+    // Task launching
+    RecentsConfiguration mConfig;
+    Rect mSearchBarBounds = new Rect();
+    Rect mTaskStackBounds = new Rect();
+    Rect mLastTaskViewBounds = new Rect();
+    TaskViewTransform mTmpTransform = new TaskViewTransform();
+    int mStatusBarHeight;
+    int mNavBarHeight;
+    int mNavBarWidth;
+    int mTaskBarHeight;
+
+    // Header (for transition)
+    TaskViewHeader mHeaderBar;
+    final Object mHeaderBarLock = new Object();
+    TaskStackView mDummyStackView;
+
+    // Variables to keep track of if we need to start recents after binding
+    boolean mTriggeredFromAltTab;
+    long mLastToggleTime;
+
+    Bitmap mThumbnailTransitionBitmapCache;
+    Task mThumbnailTransitionBitmapCacheKey;
+
+
+    public RecentsImpl(Context context) {
+        mContext = context;
+        mHandler = new Handler();
+        mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
+        Resources res = mContext.getResources();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        // Initialize the static foreground thread
+        ForegroundThread.get();
+
+        // Register the task stack listener
+        mTaskStackListener = new TaskStackListenerImpl(mHandler);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.registerTaskStackListener(mTaskStackListener);
+
+        // Initialize the static configuration resources
+        mConfig = RecentsConfiguration.initialize(mContext, ssp);
+        mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+        mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
+        mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
+        mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
+        mDummyStackView = new TaskStackView(mContext, new TaskStack());
+        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
+                null, false);
+        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+
+        // When we start, preload the data associated with the previous recent tasks.
+        // We can use a new plan since the caches will be the same.
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+        loader.preloadTasks(plan, true /* isTopTaskHome */);
+        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+        launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
+        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
+        launchOpts.onlyLoadForCache = true;
+        loader.loadTasks(mContext, plan, launchOpts);
+    }
+
+    public void onBootCompleted() {
+        mBootCompleted = true;
+        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+    }
+
+    @Override
+    public void onConfigurationChanged() {
+        // Don't reuse task stack views if the configuration changes
+        mCanReuseTaskStackViews = false;
+        mConfig.updateOnConfigurationChange();
+    }
+
+    /**
+     * This is only called from the system user's Recents.  Secondary users will instead proxy their
+     * visibility change events through to the system user via
+     * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
+     */
+    public void onVisibilityChanged(Context context, boolean visible) {
+        SystemUIApplication app = (SystemUIApplication) context;
+        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+        if (statusBar != null) {
+            statusBar.updateRecentsVisibility(visible);
+        }
+    }
+
+    /**
+     * This is only called from the system user's Recents.  Secondary users will instead proxy their
+     * visibility change events through to the system user via
+     * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
+     */
+    public void onStartScreenPinning(Context context) {
+        SystemUIApplication app = (SystemUIApplication) context;
+        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+        if (statusBar != null) {
+            statusBar.showScreenPinningRequest(false);
+        }
+    }
+
+    @Override
+    public void showRecents(boolean triggeredFromAltTab) {
+        mTriggeredFromAltTab = triggeredFromAltTab;
+
+        try {
+            // Check if the top task is in the home stack, and start the recents activity
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+            MutableBoolean isTopTaskHome = new MutableBoolean(true);
+            if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
+                startRecentsActivity(topTask, isTopTaskHome.value);
+            }
+        } catch (ActivityNotFoundException e) {
+            Console.logRawError("Failed to launch RecentAppsIntent", e);
+        }
+    }
+
+    @Override
+    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        if (mBootCompleted) {
+            // Defer to the activity to handle hiding recents, if it handles it, then it must still
+            // be visible
+            EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
+                    triggeredFromHomeKey));
+        }
+    }
+
+    @Override
+    public void toggleRecents() {
+        mTriggeredFromAltTab = false;
+
+        try {
+            // If the user has toggled it too quickly, then just eat up the event here (it's better
+            // than showing a janky screenshot).
+            // NOTE: Ideally, the screenshot mechanism would take the window transform into account
+            if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+                return;
+            }
+
+            // If Recents is the front most activity, then we should just communicate with it
+            // directly to launch the first task or dismiss itself
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+            MutableBoolean isTopTaskHome = new MutableBoolean(true);
+            if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
+                // Notify recents to toggle itself
+                EventBus.getDefault().post(new ToggleRecentsEvent());
+                mLastToggleTime = SystemClock.elapsedRealtime();
+                return;
+            } else {
+                // Otherwise, start the recents activity
+                startRecentsActivity(topTask, isTopTaskHome.value);
+            }
+        } catch (ActivityNotFoundException e) {
+            Console.logRawError("Failed to launch RecentAppsIntent", e);
+        }
+    }
+
+    @Override
+    public void preloadRecents() {
+        // Preload only the raw task list into a new load plan (which will be consumed by the
+        // RecentsActivity) only if there is a task to animate to.
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+        MutableBoolean topTaskHome = new MutableBoolean(true);
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        sInstanceLoadPlan = loader.createLoadPlan(mContext);
+        if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
+            sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
+            loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
+            TaskStack stack = sInstanceLoadPlan.getTaskStack();
+            if (stack.getTaskCount() > 0) {
+                // We try and draw the thumbnail transition bitmap in parallel before
+                // toggle/show recents is called
+                preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
+            }
+        }
+    }
+
+    @Override
+    public void cancelPreloadingRecents() {
+        // Do nothing
+    }
+
+    public void showRelativeAffiliatedTask(boolean showNextTask) {
+        // Return early if there is no focused stack
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        int focusedStackId = ssp.getFocusedStack();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+        loader.preloadTasks(plan, true /* isTopTaskHome */);
+        TaskStack focusedStack = plan.getTaskStack();
+
+        // Return early if there are no tasks in the focused stack
+        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
+
+        ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
+        // Return early if there is no running task (can't determine affiliated tasks in this case)
+        if (runningTask == null) return;
+        // Return early if the running task is in the home stack (optimization)
+        if (ssp.isInHomeStack(runningTask.id)) return;
+
+        // Find the task in the recents list
+        ArrayList<Task> tasks = focusedStack.getTasks();
+        Task toTask = null;
+        ActivityOptions launchOpts = null;
+        int taskCount = tasks.size();
+        int numAffiliatedTasks = 0;
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.key.id == runningTask.id) {
+                TaskGrouping group = task.group;
+                Task.TaskKey toTaskKey;
+                if (showNextTask) {
+                    toTaskKey = group.getNextTaskInGroup(task);
+                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                            R.anim.recents_launch_next_affiliated_task_target,
+                            R.anim.recents_launch_next_affiliated_task_source);
+                } else {
+                    toTaskKey = group.getPrevTaskInGroup(task);
+                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                            R.anim.recents_launch_prev_affiliated_task_target,
+                            R.anim.recents_launch_prev_affiliated_task_source);
+                }
+                if (toTaskKey != null) {
+                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
+                }
+                numAffiliatedTasks = group.getTaskCount();
+                break;
+            }
+        }
+
+        // Return early if there is no next task
+        if (toTask == null) {
+            if (numAffiliatedTasks > 1) {
+                if (showNextTask) {
+                    ssp.startInPlaceAnimationOnFrontMostApplication(
+                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                    R.anim.recents_launch_next_affiliated_task_bounce));
+                } else {
+                    ssp.startInPlaceAnimationOnFrontMostApplication(
+                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                    R.anim.recents_launch_prev_affiliated_task_bounce));
+                }
+            }
+            return;
+        }
+
+        // Keep track of actually launched affiliated tasks
+        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
+
+        // Launch the task
+        if (toTask.isActive) {
+            // Bring an active task to the foreground
+            ssp.moveTaskToFront(toTask.key.id, launchOpts);
+        } else {
+            ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
+        }
+    }
+
+    public void showNextAffiliatedTask() {
+        // Keep track of when the affiliated task is triggered
+        MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
+        showRelativeAffiliatedTask(true);
+    }
+
+    public void showPrevAffiliatedTask() {
+        // Keep track of when the affiliated task is triggered
+        MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
+        showRelativeAffiliatedTask(false);
+    }
+
+    /**
+     * Returns the preloaded load plan and invalidates it.
+     */
+    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
+        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
+        sInstanceLoadPlan = null;
+        return plan;
+    }
+
+    /**
+     * Prepares the header bar layout for the next transition, if the task view bounds has changed
+     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
+     *
+     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
+     *                               is not already bound (can be expensive)
+     */
+    private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Rect windowRect = ssp.getWindowRect();
+
+        // Update the configuration for the current state
+        mConfig.update(mContext, ssp, ssp.getWindowRect());
+
+        if (tryAndBindSearchWidget) {
+            // Try and pre-emptively bind the search widget on startup to ensure that we
+            // have the right thumbnail bounds to animate to.
+            // Note: We have to reload the widget id before we get the task stack bounds below
+            if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
+                mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
+            }
+        }
+        Rect systemInsets = new Rect(0, mStatusBarHeight,
+                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
+                (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
+        mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
+                mSearchBarBounds, mTaskStackBounds);
+
+        // Rebind the header bar and draw it for the transition
+        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
+        Rect taskStackBounds = new Rect(mTaskStackBounds);
+        algo.setSystemInsets(systemInsets);
+        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
+        Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
+        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
+            mLastTaskViewBounds.set(taskViewBounds);
+
+            int taskViewWidth = taskViewBounds.width();
+            synchronized (mHeaderBarLock) {
+                mHeaderBar.measure(
+                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
+                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
+                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
+            }
+        }
+    }
+
+    /**
+     * Preloads the icon of a task.
+     */
+    private void preloadIcon(ActivityManager.RunningTaskInfo task) {
+        // Ensure that we load the running task's icon
+        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+        launchOpts.runningTaskId = task.id;
+        launchOpts.loadThumbnails = false;
+        launchOpts.onlyLoadForCache = true;
+        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+    }
+
+    /**
+     * Caches the header thumbnail used for a window animation asynchronously into
+     * {@link #mThumbnailTransitionBitmapCache}.
+     */
+    private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
+            TaskStack stack, TaskStackView stackView) {
+        preloadIcon(topTask);
+
+        // Update the header bar if necessary
+        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
+
+        // Update the destination rect
+        mDummyStackView.updateMinMaxScrollForStack(stack);
+        final Task toTask = new Task();
+        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+                topTask.id, toTask);
+        ForegroundThread.getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mThumbnailTransitionBitmapCache = transitionBitmap;
+                        mThumbnailTransitionBitmapCacheKey = toTask;
+                    }
+                });
+            }
+        });
+    }
+
+    /**
+     * Creates the activity options for a unknown state->recents transition.
+     */
+    private ActivityOptions getUnknownTransitionActivityOptions() {
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_unknown_enter,
+                R.anim.recents_from_unknown_exit,
+                mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for a home->recents transition.
+     */
+    private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
+        if (fromSearchHome) {
+            return ActivityOptions.makeCustomAnimation(mContext,
+                    R.anim.recents_from_search_launcher_enter,
+                    R.anim.recents_from_search_launcher_exit,
+                    mHandler, this);
+        }
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_launcher_enter,
+                R.anim.recents_from_launcher_exit,
+                mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for an app->recents transition.
+     */
+    private ActivityOptions getThumbnailTransitionActivityOptions(
+            ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
+
+        // Update the destination rect
+        Task toTask = new Task();
+        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+                topTask.id, toTask);
+        RectF toTaskRect = toTransform.rect;
+        Bitmap thumbnail;
+        if (mThumbnailTransitionBitmapCacheKey != null
+                && mThumbnailTransitionBitmapCacheKey.key != null
+                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
+            thumbnail = mThumbnailTransitionBitmapCache;
+            mThumbnailTransitionBitmapCacheKey = null;
+            mThumbnailTransitionBitmapCache = null;
+        } else {
+            preloadIcon(topTask);
+            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+        }
+        if (thumbnail != null) {
+            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+                    thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
+                    (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
+        }
+
+        // If both the screenshot and thumbnail fails, then just fall back to the default transition
+        return getUnknownTransitionActivityOptions();
+    }
+
+    /**
+     * Returns the transition rect for the given task id.
+     */
+    private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
+            TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
+        // Find the running task in the TaskStack
+        Task task = null;
+        ArrayList<Task> tasks = stack.getTasks();
+        if (runningTaskId != -1) {
+            // Otherwise, try and find the task with the
+            int taskCount = tasks.size();
+            for (int i = taskCount - 1; i >= 0; i--) {
+                Task t = tasks.get(i);
+                if (t.key.id == runningTaskId) {
+                    task = t;
+                    runningTaskOut.copyFrom(t);
+                    break;
+                }
+            }
+        }
+        if (task == null) {
+            // If no task is specified or we can not find the task just use the front most one
+            task = tasks.get(tasks.size() - 1);
+            runningTaskOut.copyFrom(task);
+        }
+
+        // Get the transform for the running task
+        stackView.getScroller().setStackScrollToInitialState();
+        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+                stackView.getScroller().getStackScroll(), mTmpTransform, null);
+        return mTmpTransform;
+    }
+
+    /**
+     * Draws the header of a task used for the window animation into a bitmap.
+     */
+    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
+        if (toTransform != null && toTask.key != null) {
+            Bitmap thumbnail;
+            synchronized (mHeaderBarLock) {
+                int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
+                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
+                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
+                        Bitmap.Config.ARGB_8888);
+                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+                    thumbnail.eraseColor(0xFFff0000);
+                } else {
+                    Canvas c = new Canvas(thumbnail);
+                    c.scale(toTransform.scale, toTransform.scale);
+                    mHeaderBar.rebindToTask(toTask);
+                    mHeaderBar.draw(c);
+                    c.setBitmap(null);
+                }
+            }
+            return thumbnail.createAshmemBitmap();
+        }
+        return null;
+    }
+
+    /**
+     * Shows the recents activity
+     */
+    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+            boolean isTopTaskHome) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+
+        // Update the header bar if necessary
+        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
+
+        if (sInstanceLoadPlan == null) {
+            // Create a new load plan if onPreloadRecents() was never triggered
+            sInstanceLoadPlan = loader.createLoadPlan(mContext);
+        }
+
+        if (!sInstanceLoadPlan.hasTasks()) {
+            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+        }
+        TaskStack stack = sInstanceLoadPlan.getTaskStack();
+
+        // Prepare the dummy stack for the transition
+        mDummyStackView.updateMinMaxScrollForStack(stack);
+        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+                mDummyStackView.computeStackVisibilityReport();
+        boolean hasRecentTasks = stack.getTaskCount() > 0;
+        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
+
+        if (useThumbnailTransition) {
+            // Try starting with a thumbnail transition
+            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
+                    mDummyStackView);
+            if (opts != null) {
+                startRecentsActivity(topTask, opts, false /* fromHome */,
+                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
+            } else {
+                // Fall through below to the non-thumbnail transition
+                useThumbnailTransition = false;
+            }
+        }
+
+        if (!useThumbnailTransition) {
+            // If there is no thumbnail transition, but is launching from home into recents, then
+            // use a quick home transition and do the animation from home
+            if (hasRecentTasks) {
+                String homeActivityPackage = ssp.getHomeActivityPackageName();
+                String searchWidgetPackage = Prefs.getString(mContext,
+                        Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+
+                // Determine whether we are coming from a search owned home activity
+                boolean fromSearchHome = (homeActivityPackage != null) &&
+                        homeActivityPackage.equals(searchWidgetPackage);
+                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
+                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
+                        false /* fromThumbnail */, stackVr);
+            } else {
+                // Otherwise we do the normal fade from an unknown source
+                ActivityOptions opts = getUnknownTransitionActivityOptions();
+                startRecentsActivity(topTask, opts, true /* fromHome */,
+                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
+            }
+        }
+        mLastToggleTime = SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Starts the recents activity.
+     */
+    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
+              TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+        mStartAnimationTriggered = false;
+
+        // Update the configuration based on the launch options
+        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        launchState.launchedFromHome = fromSearchHome || fromHome;
+        launchState.launchedFromSearchHome = fromSearchHome;
+        launchState.launchedFromAppWithThumbnail = fromThumbnail;
+        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
+        launchState.launchedWithAltTab = mTriggeredFromAltTab;
+        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
+        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
+        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
+        launchState.launchedHasConfigurationChanged = false;
+
+        Intent intent = new Intent();
+        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+        if (opts != null) {
+            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
+        } else {
+            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        }
+        mCanReuseTaskStackViews = true;
+    }
+
+    /**** OnAnimationStartedListener Implementation ****/
+
+    @Override
+    public void onAnimationStarted() {
+        // Notify recents to start the enter animation
+        if (!mStartAnimationTriggered) {
+            mStartAnimationTriggered = true;
+            EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index d3c65d2..31ee8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.recents;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -33,10 +30,12 @@
 import android.widget.Toast;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.views.RecentsView;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+
 /**
  * A helper for the dialogs that show when task debugging is on.
  */
@@ -80,7 +79,6 @@
     private View mResizeTaskDialogContent;
     private RecentsActivity mRecentsActivity;
     private RecentsView mRecentsView;
-    private SystemServicesProxy mSsp;
     private Rect[] mBounds = {new Rect(), new Rect(), new Rect(), new Rect()};
     private Task[] mTasks = {null, null, null, null};
 
@@ -93,7 +91,6 @@
     public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) {
         mFragmentManager = mgr;
         mRecentsActivity = activity;
-        mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
     }
 
     /** Shows the resize-task dialog. */
@@ -144,7 +141,8 @@
 
     /** Helper function to place window(s) on the display according to an arrangement request. */
     private void placeTasks(int arrangement) {
-        Rect rect = mSsp.getDisplayRect();
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Rect rect = ssp.getDisplayRect();
         for (int i = 0; i < mBounds.length; ++i) {
             mBounds[i].set(rect);
             if (i != 0) {
@@ -240,7 +238,7 @@
         // current app configuration.
         for (int i = additionalTasks; i >= 0; --i) {
             if (mTasks[i] != null) {
-                mSsp.setTaskResizeable(mTasks[i].key.id);
+                ssp.setTaskResizeable(mTasks[i].key.id);
             }
         }
 
@@ -278,8 +276,9 @@
 
         if (mTasks[0].key.stackId != DOCKED_STACK_ID) {
             int taskId = mTasks[0].key.id;
-            mSsp.setTaskResizeable(taskId);
-            mSsp.dockTask(taskId, createMode);
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            ssp.setTaskResizeable(taskId);
+            ssp.dockTask(taskId, createMode);
             mRecentsView.launchTask(mTasks[0], null, DOCKED_STACK_ID);
         } else {
             Toast.makeText(getContext(), "Already docked", Toast.LENGTH_SHORT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
new file mode 100644
index 0000000..fb21500
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+/**
+ * An implementation of the system user's Recents interface to be called remotely by secondary
+ * users.
+ */
+public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub {
+
+    private static final String TAG = "RecentsSystemUser";
+
+    private Context mContext;
+    private RecentsImpl mImpl;
+    private final SparseArray<IRecentsNonSystemUserCallbacks> mNonSystemUserRecents =
+            new SparseArray<>();
+
+    public RecentsSystemUser(Context context, RecentsImpl impl) {
+        mContext = context;
+        mImpl = impl;
+    }
+
+    @Override
+    public void registerNonSystemUserCallbacks(final IBinder nonSystemUserCallbacks, int userId) {
+        try {
+            final IRecentsNonSystemUserCallbacks callback =
+                    IRecentsNonSystemUserCallbacks.Stub.asInterface(nonSystemUserCallbacks);
+            nonSystemUserCallbacks.linkToDeath(new IBinder.DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    mNonSystemUserRecents.removeAt(mNonSystemUserRecents.indexOfValue(callback));
+                }
+            }, 0);
+            mNonSystemUserRecents.put(userId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register NonSystemUserCallbacks", e);
+        }
+    }
+
+    public IRecentsNonSystemUserCallbacks getNonSystemUserRecentsForUser(int userId) {
+        return mNonSystemUserRecents.get(userId);
+    }
+
+    @Override
+    public void updateRecentsVisibility(boolean visible) {
+        mImpl.onVisibilityChanged(mContext, visible);
+    }
+
+    @Override
+    public void startScreenPinning() {
+        mImpl.onStartScreenPinning(mContext);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
new file mode 100644
index 0000000..39d0d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import com.android.systemui.SystemUIApplication;
+
+/**
+ * A strictly system-user service that is started by the secondary user's Recents (with a limited
+ * lifespan), to get the interface that the secondary user's Recents can call through to the system
+ * user's Recents.
+ */
+public class RecentsSystemUserService extends Service {
+
+    private static final String TAG = "RecentsSystemUserService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        SystemUIApplication app = (SystemUIApplication) getApplication();
+        Recents recents = app.getComponent(Recents.class);
+        if (DEBUG) {
+            Log.d(TAG, "onBind: " + recents);
+        }
+        if (recents != null) {
+            return recents.getSystemUserCallbacks();
+        }
+        return null;
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
deleted file mode 100644
index 5eefbc7..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
+++ /dev/null
@@ -1,67 +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.systemui.recents;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * A proxy for Recents events which happens strictly for non-owner users.
- */
-public class RecentsUserEventProxyReceiver extends BroadcastReceiver {
-    final public static String ACTION_PROXY_SHOW_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.SHOW_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_HIDE_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.HIDE_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_TOGGLE_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.TOGGLE_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_PRELOAD_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.PRELOAD_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_CONFIG_CHANGE_TO_USER =
-            "com.android.systemui.recents.action.CONFIG_CHANGED_FOR_USER";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Recents recents = Recents.getInstanceAndStartIfNeeded(context);
-        switch (intent.getAction()) {
-            case ACTION_PROXY_SHOW_RECENTS_TO_USER: {
-                boolean triggeredFromAltTab = intent.getBooleanExtra(
-                        Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
-                recents.showRecentsInternal(triggeredFromAltTab);
-                break;
-            }
-            case ACTION_PROXY_HIDE_RECENTS_TO_USER: {
-                boolean triggeredFromAltTab = intent.getBooleanExtra(
-                        Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
-                boolean triggeredFromHome = intent.getBooleanExtra(
-                        Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
-                recents.hideRecentsInternal(triggeredFromAltTab, triggeredFromHome);
-                break;
-            }
-            case ACTION_PROXY_TOGGLE_RECENTS_TO_USER:
-                recents.toggleRecentsInternal();
-                break;
-            case ACTION_PROXY_PRELOAD_RECENTS_TO_USER:
-                recents.preloadRecentsInternal();
-                break;
-            case ACTION_PROXY_CONFIG_CHANGE_TO_USER:
-                recents.configurationChanged();
-                break;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index ef543d0..fec0fc5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -208,7 +208,7 @@
      *
      * Events should not be edited by subscribers.
      */
-    public static class Event {
+    public static class Event implements Cloneable {
         // Indicates that this event's dispatch should be traced and logged to logcat
         boolean trace;
         // Indicates that this event must be posted on the EventBus's looper thread before invocation
@@ -218,6 +218,14 @@
 
         // Only accessible from derived events
         protected Event() {}
+
+        @Override
+        protected Object clone() throws CloneNotSupportedException {
+            Event evt = (Event) super.clone();
+            // When cloning an event, reset the cancelled-dispatch state
+            evt.cancelled = false;
+            return evt;
+        }
     }
 
     /**
diff --git a/tools/aapt2/Compat_test.cpp b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
similarity index 68%
copy from tools/aapt2/Compat_test.cpp
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
index 96aee44..5f3e830 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
@@ -14,20 +14,13 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+package com.android.systemui.recents.events.activity;
 
-namespace aapt {
+import com.android.systemui.recents.events.EventBus;
 
-TEST(CompatTest, VersionAttributesInStyle) {
+/**
+ * This is sent when the task animation when dismissing Recents starts.
+ */
+public class DismissRecentsToHomeAnimationStarted extends EventBus.Event {
+    // Simple event
 }
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Compat_test.cpp b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
similarity index 68%
copy from tools/aapt2/Compat_test.cpp
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
index 96aee44..f187178 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
@@ -14,20 +14,13 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+package com.android.systemui.recents.events.activity;
 
-namespace aapt {
+import com.android.systemui.recents.events.EventBus;
 
-TEST(CompatTest, VersionAttributesInStyle) {
+/**
+ * This is sent when the window animation into Recents starts.
+ */
+public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
+    // Simple event
 }
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
-
-} // namespace aapt
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java
new file mode 100644
index 0000000..bf9b421
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the user taps on the Home button or finishes alt-tabbing to hide the Recents
+ * activity.
+ */
+public class HideRecentsEvent extends EventBus.Event {
+
+    public final boolean triggeredFromAltTab;
+    public final boolean triggeredFromHomeKey;
+
+    public HideRecentsEvent(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        this.triggeredFromAltTab = triggeredFromAltTab;
+        this.triggeredFromHomeKey = triggeredFromHomeKey;
+    }
+}
diff --git a/tools/aapt2/Compat_test.cpp b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
similarity index 68%
copy from tools/aapt2/Compat_test.cpp
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
index 96aee44..49655b4 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
@@ -14,20 +14,13 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+package com.android.systemui.recents.events.activity;
 
-namespace aapt {
+import com.android.systemui.recents.events.EventBus;
 
-TEST(CompatTest, VersionAttributesInStyle) {
+/**
+ * This is sent when the user taps on the Overview button to toggle the Recents activity.
+ */
+public class ToggleRecentsEvent extends EventBus.Event {
+    // Simple event
 }
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
-
-} // namespace aapt
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
new file mode 100644
index 0000000..898d1fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.component;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+
+/**
+ * This is sent when the visibility of the RecentsActivity for the current user changes.
+ */
+public class RecentsVisibilityChangedEvent extends EventBus.Event {
+
+    public final Context applicationContext;
+    public final SystemServicesProxy systemServicesProxy;
+    public final boolean visible;
+
+    public RecentsVisibilityChangedEvent(Context context, SystemServicesProxy systemServicesProxy,
+            boolean visible) {
+        this.applicationContext = context.getApplicationContext();
+        this.systemServicesProxy = systemServicesProxy;
+        this.visible = visible;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
new file mode 100644
index 0000000..5cb4ccf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.component;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+
+/**
+ * This is sent when we want to start screen pinning.
+ */
+public class ScreenPinningRequestEvent extends EventBus.Event {
+
+    public final Context applicationContext;
+    public final SystemServicesProxy systemServicesProxy;
+
+    public ScreenPinningRequestEvent(Context context, SystemServicesProxy systemServicesProxy) {
+        this.applicationContext = context.getApplicationContext();
+        this.systemServicesProxy = systemServicesProxy;
+    }
+}
diff --git a/tools/aapt2/Compat_test.cpp b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
similarity index 68%
copy from tools/aapt2/Compat_test.cpp
copy to packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
index 96aee44..6e6cd84 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
@@ -14,20 +14,13 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+package com.android.systemui.recents.events.ui;
 
-namespace aapt {
+import com.android.systemui.recents.events.EventBus;
 
-TEST(CompatTest, VersionAttributesInStyle) {
+/**
+ * This is sent whenever the user interacts with the activity.
+ */
+public class UserInteractionEvent extends EventBus.Event {
+    // Simple event
 }
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
-
-} // namespace aapt
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
new file mode 100644
index 0000000..8dc2983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
+ * foreground thread for each process.
+ */
+public final class ForegroundThread extends HandlerThread {
+    private static ForegroundThread sInstance;
+    private static Handler sHandler;
+
+    private ForegroundThread() {
+        super("recents.fg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new ForegroundThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+        }
+    }
+
+    public static ForegroundThread get() {
+        synchronized (ForegroundThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (ForegroundThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 568d2b1..a51e475 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.recents.misc;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.ActivityOptions;
@@ -49,12 +46,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.MutableBoolean;
@@ -63,10 +59,11 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 import com.android.internal.app.AssistUtils;
+import com.android.internal.os.BackgroundThread;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsImpl;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -74,6 +71,9 @@
 import java.util.List;
 import java.util.Random;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
  * a point of injection when testing UI.
@@ -82,12 +82,8 @@
     final static String TAG = "SystemServicesProxy";
 
     final static BitmapFactory.Options sBitmapOptions;
-    final static HandlerThread sBgThread;
 
     static {
-        sBgThread = new HandlerThread("Recents-SystemServicesProxy",
-                android.os.Process.THREAD_PRIORITY_BACKGROUND);
-        sBgThread.start();
         sBitmapOptions = new BitmapFactory.Options();
         sBitmapOptions.inMutable = true;
     }
@@ -100,12 +96,11 @@
     IPackageManager mIpm;
     AssistUtils mAssistUtils;
     WindowManager mWm;
+    UserManager mUm;
     Display mDisplay;
     String mRecentsPackage;
     ComponentName mAssistComponent;
 
-    Handler mBgThreadHandler;
-
     Bitmap mDummyIcon;
     int mDummyThumbnailWidth;
     int mDummyThumbnailHeight;
@@ -122,9 +117,9 @@
         mIpm = AppGlobals.getPackageManager();
         mAssistUtils = new AssistUtils(context);
         mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mUm = UserManager.get(context);
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
-        mBgThreadHandler = new Handler(sBgThread.getLooper());
 
         // Get the dummy thumbnail width/heights
         Resources res = context.getResources();
@@ -244,8 +239,8 @@
             ComponentName topActivity = topTask.topActivity;
 
             // Check if the front most activity is recents
-            if (topActivity.getPackageName().equals(Recents.sRecentsPackage) &&
-                    topActivity.getClassName().equals(Recents.sRecentsActivity)) {
+            if (topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) &&
+                    topActivity.getClassName().equals(RecentsImpl.RECENTS_ACTIVITY)) {
                 if (isHomeTopMost != null) {
                     isHomeTopMost.value = false;
                 }
@@ -415,7 +410,7 @@
         if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
 
         // Remove the task.
-        mBgThreadHandler.post(new Runnable() {
+        BackgroundThread.getHandler().post(new Runnable() {
             @Override
             public void run() {
                 mAm.removeTask(taskId);
@@ -552,12 +547,27 @@
     }
 
     /**
-     * Returns whether the foreground user is the owner.
+     * Returns whether the provided {@param userId} represents the system user.
      */
-    public boolean isForegroundUserSystem() {
-        if (mAm == null) return false;
+    public boolean isSystemUser(int userId) {
+        return userId == UserHandle.USER_SYSTEM;
+    }
 
-        return mAm.getCurrentUser() == UserHandle.USER_SYSTEM;
+    /**
+     * Returns the current user id.
+     */
+    public int getCurrentUser() {
+        if (mAm == null) return 0;
+
+        return mAm.getCurrentUser();
+    }
+
+    /**
+     * Returns the processes user id.
+     */
+    public int getProcessUser() {
+        if (mUm == null) return 0;
+        return mUm.getUserHandle();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 93c5ee7..2bf2ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -18,13 +18,10 @@
 
 import android.animation.Animator;
 import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.View;
 import android.view.ViewParent;
 
-import java.util.ArrayList;
-
 /* Common code */
 public class Utilities {
 
@@ -45,93 +42,19 @@
     }
 
     /** Scales a rect about its centroid */
-    public static void scaleRectAboutCenter(Rect r, float scale) {
+    public static void scaleRectAboutCenter(RectF r, float scale) {
         if (scale != 1.0f) {
-            int cx = r.centerX();
-            int cy = r.centerY();
+            float cx = r.centerX();
+            float cy = r.centerY();
             r.offset(-cx, -cy);
-            r.left = (int) (r.left * scale + 0.5f);
-            r.top = (int) (r.top * scale + 0.5f);
-            r.right = (int) (r.right * scale + 0.5f);
-            r.bottom = (int) (r.bottom * scale + 0.5f);
+            r.left *= scale;
+            r.top *= scale;
+            r.right *= scale;
+            r.bottom *= scale;
             r.offset(cx, cy);
         }
     }
 
-    /** Maps a coorindate in a descendant view into the parent. */
-    public static float mapCoordInDescendentToSelf(View descendant, View root,
-            float[] coord, boolean includeRootScroll) {
-        ArrayList<View> ancestorChain = new ArrayList<View>();
-
-        float[] pt = {coord[0], coord[1]};
-
-        View v = descendant;
-        while(v != root && v != null) {
-            ancestorChain.add(v);
-            v = (View) v.getParent();
-        }
-        ancestorChain.add(root);
-
-        float scale = 1.0f;
-        int count = ancestorChain.size();
-        for (int i = 0; i < count; i++) {
-            View v0 = ancestorChain.get(i);
-            // For TextViews, scroll has a meaning which relates to the text position
-            // which is very strange... ignore the scroll.
-            if (v0 != descendant || includeRootScroll) {
-                pt[0] -= v0.getScrollX();
-                pt[1] -= v0.getScrollY();
-            }
-
-            v0.getMatrix().mapPoints(pt);
-            pt[0] += v0.getLeft();
-            pt[1] += v0.getTop();
-            scale *= v0.getScaleX();
-        }
-
-        coord[0] = pt[0];
-        coord[1] = pt[1];
-        return scale;
-    }
-
-    /** Maps a coordinate in the root to a descendent. */
-    public static float mapCoordInSelfToDescendent(View descendant, View root,
-            float[] coord, Matrix tmpInverseMatrix) {
-        ArrayList<View> ancestorChain = new ArrayList<View>();
-
-        float[] pt = {coord[0], coord[1]};
-
-        View v = descendant;
-        while(v != root) {
-            ancestorChain.add(v);
-            v = (View) v.getParent();
-        }
-        ancestorChain.add(root);
-
-        float scale = 1.0f;
-        int count = ancestorChain.size();
-        tmpInverseMatrix.set(Matrix.IDENTITY_MATRIX);
-        for (int i = count - 1; i >= 0; i--) {
-            View ancestor = ancestorChain.get(i);
-            View next = i > 0 ? ancestorChain.get(i-1) : null;
-
-            pt[0] += ancestor.getScrollX();
-            pt[1] += ancestor.getScrollY();
-
-            if (next != null) {
-                pt[0] -= next.getLeft();
-                pt[1] -= next.getTop();
-                next.getMatrix().invert(tmpInverseMatrix);
-                tmpInverseMatrix.mapPoints(pt);
-                scale *= next.getScaleX();
-            }
-        }
-
-        coord[0] = pt[0];
-        coord[1] = pt[1];
-        return scale;
-    }
-
     /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
     public static float computeContrastBetweenColors(int bg, int fg) {
         float bgR = Color.red(bg) / 255f;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
deleted file mode 100644
index 624a8ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
+++ /dev/null
@@ -1,28 +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.systemui.recents.model;
-
-import android.graphics.Bitmap;
-
-/**
- * The Bitmap LRU cache.
- */
-class BitmapLruCache extends KeyStoreLruCache<Bitmap> {
-    public BitmapLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
deleted file mode 100644
index 01a515b..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
+++ /dev/null
@@ -1,28 +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.systemui.recents.model;
-
-import android.graphics.drawable.Drawable;
-
-/**
- * The Drawable LRU cache.
- */
-class DrawableLruCache extends KeyStoreLruCache<Drawable> {
-    public DrawableLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
deleted file mode 100644
index 97e0916..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
+++ /dev/null
@@ -1,94 +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.systemui.recents.model;
-
-import android.util.LruCache;
-
-import java.util.HashMap;
-
-/**
- * An LRU cache that internally support querying the keys as well as values.  We use this to keep
- * track of the task metadata to determine when to invalidate the cache when tasks have been
- * updated. Generally, this cache will return the last known cache value for the requested task
- * key.
- */
-public class KeyStoreLruCache<V> {
-    // We keep a set of keys that are associated with the LRU cache, so that we can find out
-    // information about the Task that was previously in the cache.
-    HashMap<Integer, Task.TaskKey> mTaskKeys = new HashMap<Integer, Task.TaskKey>();
-    // The cache implementation, mapping task id -> value
-    LruCache<Integer, V> mCache;
-
-    public KeyStoreLruCache(int cacheSize) {
-        mCache = new LruCache<Integer, V>(cacheSize) {
-
-            @Override
-            protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
-                mTaskKeys.remove(taskId);
-            }
-        };
-    }
-
-    /** Gets a specific entry in the cache. */
-    final V get(Task.TaskKey key) {
-        return mCache.get(key.id);
-    }
-
-    /**
-     * Returns the value only if the Task has not updated since the last time it was in the cache.
-     */
-    final V getAndInvalidateIfModified(Task.TaskKey key) {
-        Task.TaskKey lastKey = mTaskKeys.get(key.id);
-        if (lastKey != null && (lastKey.lastActiveTime < key.lastActiveTime)) {
-            // The task has updated (been made active since the last time it was put into the
-            // LRU cache) so invalidate that item in the cache
-            remove(key);
-            return null;
-        }
-        // Either the task does not exist in the cache, or the last active time is the same as
-        // the key specified, so return what is in the cache
-        return mCache.get(key.id);
-    }
-
-    /** Puts an entry in the cache for a specific key. */
-    final void put(Task.TaskKey key, V value) {
-        mCache.put(key.id, value);
-        mTaskKeys.put(key.id, key);
-    }
-
-    /** Removes a cache entry for a specific key. */
-    final void remove(Task.TaskKey key) {
-        mCache.remove(key.id);
-        mTaskKeys.remove(key.id);
-    }
-
-    /** Removes all the entries in the cache. */
-    final void evictAll() {
-        mCache.evictAll();
-        mTaskKeys.clear();
-    }
-
-    /** Returns the size of the cache. */
-    final int size() {
-        return mCache.size();
-    }
-
-    /** Trims the cache to a specific size */
-    final void trimToSize(int cacheSize) {
-        mCache.resize(cacheSize);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 8f9a293..d9057b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -16,17 +16,12 @@
 
 package com.android.systemui.recents.model;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.os.Looper;
 import android.os.UserHandle;
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-
-import java.util.HashSet;
-import java.util.List;
 
 /**
  * The package monitor listens for changes from PackageManager to update the contents of the
@@ -38,8 +33,9 @@
     public void register(Context context) {
         try {
             // We register for events from all users, but will cross-reference them with
-            // packages for the current user and any profiles they have
-            register(context, Looper.getMainLooper(), UserHandle.ALL, true);
+            // packages for the current user and any profiles they have.  Ensure that events are
+            // handled in a background thread.
+            register(context, BackgroundThread.get().getLooper(), UserHandle.ALL, true);
         } catch (IllegalStateException e) {
             e.printStackTrace();
         }
@@ -57,9 +53,9 @@
 
     @Override
     public void onPackageRemoved(String packageName, int uid) {
-        // Notify callbacks that a package has changed
+        // Notify callbacks on the main thread that a package has changed
         final int eventUserId = getChangingUserId();
-        EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
+        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
     }
 
     @Override
@@ -70,39 +66,8 @@
 
     @Override
     public void onPackageModified(String packageName) {
-        // Notify callbacks that a package has changed
+        // Notify callbacks on the main thread that a package has changed
         final int eventUserId = getChangingUserId();
-        EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
-    }
-
-    /**
-     * Computes the components that have been removed as a result of a change in the specified
-     * package.
-     */
-    public HashSet<ComponentName> computeComponentsRemoved(List<Task.TaskKey> taskKeys,
-            String packageName, int userId) {
-        // Identify all the tasks that should be removed as a result of the package being removed.
-        // Using a set to ensure that we callback once per unique component.
-        HashSet<ComponentName> existingComponents = new HashSet<ComponentName>();
-        HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
-        for (Task.TaskKey t : taskKeys) {
-            // Skip if this doesn't apply to the current user
-            if (t.userId != userId) continue;
-
-            ComponentName cn = t.baseIntent.getComponent();
-            if (cn.getPackageName().equals(packageName)) {
-                if (existingComponents.contains(cn)) {
-                    // If we know that the component still exists in the package, then skip
-                    continue;
-                }
-                SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
-                if (ssp.getActivityInfo(cn, userId) != null) {
-                    existingComponents.add(cn);
-                } else {
-                    removedComponents.add(cn);
-                }
-            }
-        }
-        return removedComponents;
+        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6ef7253..8de8e15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -23,12 +23,12 @@
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.util.Log;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 
 
@@ -41,8 +41,10 @@
  *      options specified, such that we can transition into the Recents activity seamlessly
  */
 public class RecentsTaskLoadPlan {
-    static String TAG = "RecentsTaskLoadPlan";
-    static boolean DEBUG = false;
+    private static String TAG = "RecentsTaskLoadPlan";
+    private static boolean DEBUG = false;
+
+    private static int INVALID_TASK_ID = -1;
 
     /** The set of conditions to load tasks. */
     public static class Options {
@@ -57,25 +59,22 @@
 
     Context mContext;
     RecentsConfiguration mConfig;
-    SystemServicesProxy mSystemServicesProxy;
 
     List<ActivityManager.RecentTaskInfo> mRawTasks;
     TaskStack mStack;
-    HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
-            new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
 
     /** Package level ctor */
-    RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp) {
+    RecentsTaskLoadPlan(Context context, RecentsConfiguration config) {
         mContext = context;
         mConfig = config;
-        mSystemServicesProxy = ssp;
     }
 
     /**
      * An optimization to preload the raw list of tasks.
      */
     public synchronized void preloadRawTasks(boolean isTopTaskHome) {
-        mRawTasks = mSystemServicesProxy.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
                 UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
         Collections.reverse(mRawTasks);
 
@@ -87,12 +86,10 @@
      * have a list of all the recent tasks with their metadata, not including icons or
      * thumbnails which were not cached and have to be loaded.
      */
-    synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
+    public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
         if (DEBUG) Log.d(TAG, "preloadPlan");
 
-        // This activity info cache will be used for both preloadPlan() and executePlan()
-        mActivityInfoCache.clear();
-
+        SystemServicesProxy ssp = Recents.getSystemServices();
         Resources res = mContext.getResources();
         ArrayList<Task> stackTasks = new ArrayList<>();
         if (mRawTasks == null) {
@@ -106,30 +103,14 @@
             Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
                     t.userId, t.firstActiveTime, t.lastActiveTime);
 
-            // Get an existing activity info handle if possible
-            Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
-            ActivityInfoHandle infoHandle;
-            boolean hadCachedActivityInfo = false;
-            if (mActivityInfoCache.containsKey(cnKey)) {
-                infoHandle = mActivityInfoCache.get(cnKey);
-                hadCachedActivityInfo = true;
-            } else {
-                infoHandle = new ActivityInfoHandle();
-            }
-
             // Load the label, icon, and color
             String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
-                    mSystemServicesProxy, infoHandle);
+                    ssp);
             String contentDescription = loader.getAndUpdateContentDescription(taskKey,
-                    activityLabel, mSystemServicesProxy, res);
-            Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
-                    mSystemServicesProxy, res, infoHandle, false);
-            int activityColor = loader.getActivityPrimaryColor(t.taskDescription, res);
-
-            // Update the activity info cache
-            if (!hadCachedActivityInfo && infoHandle.info != null) {
-                mActivityInfoCache.put(cnKey, infoHandle);
-            }
+                    activityLabel, ssp, res);
+            Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, ssp,
+                    res, false);
+            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
 
             Bitmap icon = t.taskDescription != null
                     ? t.taskDescription.getInMemoryIcon()
@@ -139,11 +120,11 @@
                     : null;
 
             // Add the task to the stack
-            Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
-                    t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription,
-                    activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled,
-                    icon, iconFilename);
-            task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
+            Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
+                    t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
+                    activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
+                    iconFilename);
+            task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
             if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
 
             stackTasks.add(task);
@@ -158,12 +139,13 @@
     /**
      * Called to apply the actual loading based on the specified conditions.
      */
-    synchronized void executePlan(Options opts, RecentsTaskLoader loader,
+    public synchronized void executePlan(Options opts, RecentsTaskLoader loader,
             TaskResourceLoadQueue loadQueue) {
         if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks +
                 ", # thumbnails: " + opts.numVisibleTaskThumbnails +
                 ", running task id: " + opts.runningTaskId);
 
+        SystemServicesProxy ssp = Recents.getSystemServices();
         Resources res = mContext.getResources();
 
         // Iterate through each of the tasks and load them according to the load conditions.
@@ -174,17 +156,6 @@
             Task task = tasks.get(i);
             Task.TaskKey taskKey = task.key;
 
-            // Get an existing activity info handle if possible
-            Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
-            ActivityInfoHandle infoHandle;
-            boolean hadCachedActivityInfo = false;
-            if (mActivityInfoCache.containsKey(cnKey)) {
-                infoHandle = mActivityInfoCache.get(cnKey);
-                hadCachedActivityInfo = true;
-            } else {
-                infoHandle = new ActivityInfoHandle();
-            }
-
             boolean isRunningTask = (task.key.id == opts.runningTaskId);
             boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
             boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
@@ -197,26 +168,20 @@
             if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
                 if (task.activityIcon == null) {
                     if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
-                    task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
-                            t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
+                    task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
+                            ssp, res, true);
                 }
             }
             if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
                 if (task.thumbnail == null || isRunningTask) {
                     if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
                     if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
-                        task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                                mSystemServicesProxy, true);
+                        task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, true);
                     } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
                         loadQueue.addTask(task);
                     }
                 }
             }
-
-            // Update the activity info cache
-            if (!hadCachedActivityInfo && infoHandle.info != null) {
-                mActivityInfoCache.put(cnKey, infoHandle);
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 39bef81..ea97b71 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
@@ -27,20 +28,21 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.util.Log;
+import android.util.LruCache;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
+import java.util.Map;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 
-/** Handle to an ActivityInfo */
-class ActivityInfoHandle {
-    ActivityInfo info;
-}
-
-/** A bitmap load queue */
+/**
+ * A Task load queue
+ */
 class TaskResourceLoadQueue {
     ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
 
@@ -78,8 +80,10 @@
     }
 }
 
-/* Task resource loader */
-class TaskResourceLoader implements Runnable {
+/**
+ * Task resource loader
+ */
+class BackgroundTaskLoader implements Runnable {
     static String TAG = "TaskResourceLoader";
     static boolean DEBUG = false;
 
@@ -88,10 +92,9 @@
     Handler mLoadThreadHandler;
     Handler mMainThreadHandler;
 
-    SystemServicesProxy mSystemServicesProxy;
     TaskResourceLoadQueue mLoadQueue;
-    DrawableLruCache mApplicationIconCache;
-    BitmapLruCache mThumbnailCache;
+    TaskKeyLruCache<Drawable> mApplicationIconCache;
+    TaskKeyLruCache<Bitmap> mThumbnailCache;
     Bitmap mDefaultThumbnail;
     BitmapDrawable mDefaultApplicationIcon;
 
@@ -99,9 +102,9 @@
     boolean mWaitingOnLoadQueue;
 
     /** Constructor, creates a new loading thread that loads task resources in the background */
-    public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache,
-                              BitmapLruCache thumbnailCache, Bitmap defaultThumbnail,
-                              BitmapDrawable defaultApplicationIcon) {
+    public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
+            TaskKeyLruCache<Drawable> applicationIconCache, TaskKeyLruCache<Bitmap> thumbnailCache,
+            Bitmap defaultThumbnail, BitmapDrawable defaultApplicationIcon) {
         mLoadQueue = loadQueue;
         mApplicationIconCache = applicationIconCache;
         mThumbnailCache = thumbnailCache;
@@ -119,7 +122,6 @@
     void start(Context context) {
         mContext = context;
         mCancelled = false;
-        mSystemServicesProxy = new SystemServicesProxy(context);
         // Notify the load thread to start loading
         synchronized(mLoadThread) {
             mLoadThread.notifyAll();
@@ -130,7 +132,6 @@
     void stop() {
         // Mark as cancelled for the thread to pick up
         mCancelled = true;
-        mSystemServicesProxy = null;
         // If we are waiting for the load queue for more tasks, then we can just reset the
         // Context now, since nothing is using it
         if (mWaitingOnLoadQueue) {
@@ -155,7 +156,7 @@
                 }
             } else {
                 RecentsConfiguration config = RecentsConfiguration.getInstance();
-                SystemServicesProxy ssp = mSystemServicesProxy;
+                SystemServicesProxy ssp = Recents.getSystemServices();
                 // If we've stopped the loader, then fall through to the above logic to wait on
                 // the load thread
                 if (ssp != null) {
@@ -172,7 +173,7 @@
 
                             if (cachedIcon == null) {
                                 ActivityInfo info = ssp.getActivityInfo(
-                                        t.key.baseIntent.getComponent(), t.key.userId);
+                                        t.key.getComponent(), t.key.userId);
                                 if (info != null) {
                                     if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
                                     cachedIcon = ssp.getActivityIcon(info, t.key.userId);
@@ -246,201 +247,67 @@
     }
 }
 
-/* Recents task loader
- * NOTE: We should not hold any references to non-application Context from a static instance */
+/**
+ * Recents task loader
+ */
 public class RecentsTaskLoader {
+
     private static final String TAG = "RecentsTaskLoader";
+    private static final boolean DEBUG = false;
 
-    static RecentsTaskLoader sInstance;
-    static int INVALID_TASK_ID = -1;
+    // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
+    // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
+    // below, this is per-package so we can't invalidate the items in the cache based on the last
+    // active time.  Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
+    // package in the cache has been updated, so that we may remove it.
+    private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
+    private final TaskKeyLruCache<Drawable> mApplicationIconCache;
+    private final TaskKeyLruCache<Bitmap> mThumbnailCache;
+    private final TaskKeyLruCache<String> mActivityLabelCache;
+    private final TaskKeyLruCache<String> mContentDescriptionCache;
+    private final TaskResourceLoadQueue mLoadQueue;
+    private final BackgroundTaskLoader mLoader;
 
-    SystemServicesProxy mSystemServicesProxy;
-    DrawableLruCache mApplicationIconCache;
-    BitmapLruCache mThumbnailCache;
-    StringLruCache mActivityLabelCache;
-    StringLruCache mContentDescriptionCache;
-    TaskResourceLoadQueue mLoadQueue;
-    TaskResourceLoader mLoader;
+    private final int mMaxThumbnailCacheSize;
+    private final int mMaxIconCacheSize;
+    private int mNumVisibleTasksLoaded;
+    private int mNumVisibleThumbnailsLoaded;
 
-    int mMaxThumbnailCacheSize;
-    int mMaxIconCacheSize;
-    int mNumVisibleTasksLoaded;
-    int mNumVisibleThumbnailsLoaded;
-
+    int mDefaultTaskBarBackgroundColor;
     BitmapDrawable mDefaultApplicationIcon;
     Bitmap mDefaultThumbnail;
 
-    /** Private Constructor */
-    private RecentsTaskLoader(Context context) {
-        mMaxThumbnailCacheSize = context.getResources().getInteger(
-                R.integer.config_recents_max_thumbnail_count);
-        mMaxIconCacheSize = context.getResources().getInteger(
-                R.integer.config_recents_max_icon_count);
+    public RecentsTaskLoader(Context context) {
+        Resources res = context.getResources();
+        mDefaultTaskBarBackgroundColor =
+                res.getColor(R.color.recents_task_bar_default_background_color);
+        mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
+        mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
         int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxIconCacheSize;
         int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxThumbnailCacheSize;
 
         // Create the default assets
-        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        icon.eraseColor(0x00000000);
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+        icon.eraseColor(0);
         mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
         mDefaultThumbnail.setHasAlpha(false);
         mDefaultThumbnail.eraseColor(0xFFffffff);
         mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
 
         // Initialize the proxy, cache and loaders
-        mSystemServicesProxy = new SystemServicesProxy(context);
+        int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
         mLoadQueue = new TaskResourceLoadQueue();
-        mApplicationIconCache = new DrawableLruCache(iconCacheSize);
-        mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
-        mActivityLabelCache = new StringLruCache(100);
-        mContentDescriptionCache = new StringLruCache(100);
-        mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
+        mApplicationIconCache = new TaskKeyLruCache<>(iconCacheSize);
+        mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
+        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks);
+        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks);
+        mActivityInfoCache = new LruCache(numRecentTasks);
+        mLoader = new BackgroundTaskLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
                 mDefaultThumbnail, mDefaultApplicationIcon);
     }
 
-    /** Initializes the recents task loader */
-    public static RecentsTaskLoader initialize(Context context) {
-        if (sInstance == null) {
-            sInstance = new RecentsTaskLoader(context);
-        }
-        return sInstance;
-    }
-
-    /** Returns the current recents task loader */
-    public static RecentsTaskLoader getInstance() {
-        return sInstance;
-    }
-
-    /** Returns the system services proxy */
-    public SystemServicesProxy getSystemServicesProxy() {
-        return mSystemServicesProxy;
-    }
-
-    /** Returns the activity label using as many cached values as we can. */
-    public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
-            ActivityManager.TaskDescription td, SystemServicesProxy ssp,
-            ActivityInfoHandle infoHandle) {
-        // Return the task description label if it exists
-        if (td != null && td.getLabel() != null) {
-            return td.getLabel();
-        }
-        // Return the cached activity label if it exists
-        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-        // All short paths failed, load the label from the activity info and cache it
-        if (infoHandle.info == null) {
-            infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
-                    taskKey.userId);
-        }
-        if (infoHandle.info != null) {
-            label = ssp.getActivityLabel(infoHandle.info);
-            mActivityLabelCache.put(taskKey, label);
-            return label;
-        } else {
-            Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent()
-                    + " u=" + taskKey.userId);
-        }
-        // If the activity info does not exist or fails to load, return an empty label for now,
-        // but do not cache it
-        return "";
-    }
-
-    /** Returns the content description using as many cached values as we can. */
-    public String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
-            SystemServicesProxy ssp, Resources res) {
-        // Return the cached content description if it exists
-        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-        // If the given activity label is empty, don't compute or cache the content description
-        if (activityLabel.isEmpty()) {
-            return "";
-        }
-
-        label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
-        if (label != null) {
-            mContentDescriptionCache.put(taskKey, label);
-            return label;
-        } else {
-            Log.w(TAG, "Missing content description for " + taskKey.baseIntent.getComponent()
-                    + " u=" + taskKey.userId);
-        }
-        // If the content description does not exist, return an empty label for now, but do not
-        // cache it
-        return "";
-    }
-
-    /** Returns the activity icon using as many cached values as we can. */
-    public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
-            ActivityManager.TaskDescription td, SystemServicesProxy ssp,
-            Resources res, ActivityInfoHandle infoHandle, boolean loadIfNotCached) {
-        // Return the cached activity icon if it exists
-        Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
-        if (icon != null) {
-            return icon;
-        }
-
-        if (loadIfNotCached) {
-            // Return and cache the task description icon if it exists
-            Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
-                    td.getIconFilename(), ssp, res);
-            if (tdDrawable != null) {
-                mApplicationIconCache.put(taskKey, tdDrawable);
-                return tdDrawable;
-            }
-
-            // Load the icon from the activity info and cache it
-            if (infoHandle.info == null) {
-                infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
-                        taskKey.userId);
-            }
-            if (infoHandle.info != null) {
-                icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
-                if (icon != null) {
-                    mApplicationIconCache.put(taskKey, icon);
-                    return icon;
-                }
-            }
-        }
-        // We couldn't load any icon
-        return null;
-    }
-
-    /** Returns the bitmap using as many cached values as we can. */
-    public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
-            boolean loadIfNotCached) {
-        // Return the cached thumbnail if it exists
-        Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
-        if (thumbnail != null) {
-            return thumbnail;
-        }
-
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING && loadIfNotCached) {
-            // Load the thumbnail from the system
-            thumbnail = ssp.getTaskThumbnail(taskKey.id);
-            if (thumbnail != null) {
-                mThumbnailCache.put(taskKey, thumbnail);
-                return thumbnail;
-            }
-        }
-        // We couldn't load any thumbnail
-        return null;
-    }
-
-    /** Returns the activity's primary color. */
-    public int getActivityPrimaryColor(ActivityManager.TaskDescription td, Resources res) {
-        if (td != null && td.getPrimaryColor() != 0) {
-            return td.getPrimaryColor();
-        }
-        return res.getColor(R.color.recents_task_bar_default_background_color);
-    }
-
     /** Returns the size of the app icon cache. */
     public int getApplicationIconCacheSize() {
         return mMaxIconCacheSize;
@@ -454,7 +321,7 @@
     /** Creates a new plan for loading the recent tasks. */
     public RecentsTaskLoadPlan createLoadPlan(Context context) {
         RecentsConfiguration config = RecentsConfiguration.getInstance();
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config);
         return plan;
     }
 
@@ -505,17 +372,12 @@
         mLoadQueue.removeTask(t);
         mThumbnailCache.remove(t.key);
         mApplicationIconCache.remove(t.key);
+        mActivityInfoCache.remove(t.key.getComponent());
         if (notifyTaskDataUnloaded) {
             t.notifyTaskDataUnloaded(null, mDefaultApplicationIcon);
         }
     }
 
-    /** Stops the task loader and clears all pending tasks */
-    void stopLoader() {
-        mLoader.stop();
-        mLoadQueue.clearTasks();
-    }
-
     /**
      * Handles signals from the system, trimming memory when requested to prevent us from running
      * out of memory.
@@ -542,18 +404,23 @@
                 // We are leaving recents, so trim the data a bit
                 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
                 mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 2));
                 break;
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
             case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                 // We are going to be low on memory
                 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
                 mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 4));
                 break;
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
             case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                 // We are low on memory, so release everything
                 mThumbnailCache.evictAll();
                 mApplicationIconCache.evictAll();
+                mActivityInfoCache.evictAll();
                 // The cache is small, only clear the label cache when we are critical
                 mActivityLabelCache.evictAll();
                 mContentDescriptionCache.evictAll();
@@ -562,4 +429,165 @@
                 break;
         }
     }
+
+    /**
+     * Returns the cached task label if the task key is not expired, updating the cache if it is.
+     */
+    String getAndUpdateActivityLabel(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+                                     SystemServicesProxy ssp) {
+        // Return the task description label if it exists
+        if (td != null && td.getLabel() != null) {
+            return td.getLabel();
+        }
+        // Return the cached activity label if it exists
+        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+        // All short paths failed, load the label from the activity info and cache it
+        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp);
+        if (activityInfo != null) {
+            label = ssp.getActivityLabel(activityInfo);
+            mActivityLabelCache.put(taskKey, label);
+            return label;
+        }
+        // If the activity info does not exist or fails to load, return an empty label for now,
+        // but do not cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task content description if the task key is not expired, updating the
+     * cache if it is.
+     */
+    String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
+            SystemServicesProxy ssp, Resources res) {
+        // Return the cached content description if it exists
+        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+        // If the given activity label is empty, don't compute or cache the content description
+        if (activityLabel.isEmpty()) {
+            return "";
+        }
+
+        label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
+        if (label != null) {
+            mContentDescriptionCache.put(taskKey, label);
+            return label;
+        }
+        // If the content description does not exist, return an empty label for now, but do not
+        // cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
+     */
+    Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+            SystemServicesProxy ssp, Resources res, boolean loadIfNotCached) {
+        // Return the cached activity icon if it exists
+        Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
+        if (icon != null) {
+            return icon;
+        }
+
+        if (loadIfNotCached) {
+            // Return and cache the task description icon if it exists
+            icon = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
+                    td.getIconFilename(), ssp, res);
+            if (icon != null) {
+                mApplicationIconCache.put(taskKey, icon);
+                return icon;
+            }
+
+            // Load the icon from the activity info and cache it
+            ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp);
+            if (activityInfo != null) {
+                icon = ssp.getActivityIcon(activityInfo, taskKey.userId);
+                if (icon != null) {
+                    mApplicationIconCache.put(taskKey, icon);
+                    return icon;
+                }
+            }
+        }
+        // We couldn't load any icon
+        return null;
+    }
+
+    /**
+     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
+     */
+    Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
+            boolean loadIfNotCached) {
+        // Return the cached thumbnail if it exists
+        Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
+        if (thumbnail != null) {
+            return thumbnail;
+        }
+
+        if (loadIfNotCached) {
+            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
+                // Load the thumbnail from the system
+                thumbnail = ssp.getTaskThumbnail(taskKey.id);
+                if (thumbnail != null) {
+                    mThumbnailCache.put(taskKey, thumbnail);
+                    return thumbnail;
+                }
+            }
+        }
+        // We couldn't load any thumbnail
+        return null;
+    }
+
+    /**
+     * Returns the task's primary color.
+     */
+    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+        if (td != null && td.getPrimaryColor() != 0) {
+            return td.getPrimaryColor();
+        }
+        return mDefaultTaskBarBackgroundColor;
+    }
+
+    /**
+     * Returns the activity info for the given task key, retrieving one from the system if the
+     * task key is expired.
+     */
+    private ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey, SystemServicesProxy ssp) {
+        ComponentName cn = taskKey.getComponent();
+        ActivityInfo activityInfo = mActivityInfoCache.get(cn);
+        if (activityInfo == null) {
+            activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
+            mActivityInfoCache.put(cn, activityInfo);
+        }
+        return activityInfo;
+    }
+
+    /**
+     * Stops the task loader and clears all queued, pending task loads.
+     */
+    private void stopLoader() {
+        mLoader.stop();
+        mLoadQueue.clearTasks();
+    }
+
+    /**** Event Bus Events ****/
+
+    public final void onBusEvent(PackagesChangedEvent event) {
+        // Remove all the cached activity infos for this package.  The other caches do not need to
+        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
+        // cached contents are requested
+        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+        for (ComponentName cn : activityInfoCache.keySet()) {
+            if (cn.getPackageName().equals(event.packageName)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Removing activity info from cache: " + cn);
+                }
+                mActivityInfoCache.remove(cn);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
deleted file mode 100644
index 6769716..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
+++ /dev/null
@@ -1,26 +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.systemui.recents.model;
-
-/**
- * The String LRU cache.
- */
-class StringLruCache extends KeyStoreLruCache<String> {
-    public StringLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index c14adf4..0793180 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -38,38 +38,11 @@
         public void onTaskDataUnloaded();
 
         /* Notifies when a task's stack id has changed. */
-        public void onMultiStackDebugTaskStackIdChanged();
-    }
-
-    /** The ComponentNameKey represents the unique primary key for a component
-     * belonging to a specified user. */
-    public static class ComponentNameKey {
-        final ComponentName component;
-        final int userId;
-
-        public ComponentNameKey(ComponentName cn, int user) {
-            component = cn;
-            userId = user;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(component, userId);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof ComponentNameKey)) {
-                return false;
-            }
-            return component.equals(((ComponentNameKey) o).component) &&
-                    userId == ((ComponentNameKey) o).userId;
-        }
+        public void onTaskStackIdChanged();
     }
 
     /* The Task Key represents the unique primary key for the task */
     public static class TaskKey {
-        final ComponentNameKey mComponentNameKey;
         public final int id;
         public int stackId;
         public final Intent baseIntent;
@@ -79,7 +52,6 @@
 
         public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
                 long lastActiveTime) {
-            mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId);
             this.id = id;
             this.stackId = stackId;
             this.baseIntent = intent;
@@ -88,9 +60,8 @@
             this.lastActiveTime = lastActiveTime;
         }
 
-        /** Returns the component name key for this task. */
-        public ComponentNameKey getComponentNameKey() {
-            return mComponentNameKey;
+        public ComponentName getComponent() {
+            return this.baseIntent.getComponent();
         }
 
         @Override
@@ -113,7 +84,7 @@
                     + "s: " + stackId + ", "
                     + "u: " + userId + ", "
                     + "lat: " + lastActiveTime + ", "
-                    + baseIntent.getComponent().getPackageName();
+                    + getComponent().getPackageName();
         }
     }
 
@@ -134,7 +105,8 @@
     public boolean lockToTaskEnabled;
     public Bitmap icon;
     public String iconFilename;
-    TaskCallbacks mCb;
+
+    private TaskCallbacks mCb;
 
     public Task() {
         // Do nothing
@@ -194,7 +166,7 @@
     public void setStackId(int stackId) {
         key.stackId = stackId;
         if (mCb != null) {
-            mCb.onMultiStackDebugTaskStackIdChanged();
+            mCb.onTaskStackIdChanged();
         }
     }
 
@@ -229,7 +201,7 @@
         if (group != null) {
             groupAffiliation = Integer.toString(group.affiliation);
         }
-        return "Task (" + groupAffiliation + "): " + key.baseIntent.getComponent().getPackageName() +
+        return "Task (" + groupAffiliation + "): " + key.getComponent().getPackageName() +
                 " [" + super.toString() + "]";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
new file mode 100644
index 0000000..6d11f14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
@@ -0,0 +1,94 @@
+/*
+ * 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.systemui.recents.model;
+
+import android.util.LruCache;
+import android.util.SparseArray;
+
+/**
+ * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
+ * recently referenced key/values will be evicted as more values than the given cache size are
+ * inserted.
+ *
+ * In addition, this also allows the caller to invalidate cached values for keys that have since
+ * changed.
+ */
+public class TaskKeyLruCache<V> {
+
+    private final SparseArray<Task.TaskKey> mKeys = new SparseArray<>();
+    private final LruCache<Integer, V> mCache;
+
+    public TaskKeyLruCache(int cacheSize) {
+        mCache = new LruCache<Integer, V>(cacheSize) {
+
+            @Override
+            protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
+                mKeys.remove(taskId);
+            }
+        };
+    }
+
+    /**
+     * Gets a specific entry in the cache with the specified key, regardless of whether the cached
+     * value is valid or not.
+     */
+    final V get(Task.TaskKey key) {
+        return mCache.get(key.id);
+    }
+
+    /**
+     * Returns the value only if the key is valid (has not been updated since the last time it was
+     * in the cache)
+     */
+    final V getAndInvalidateIfModified(Task.TaskKey key) {
+        Task.TaskKey lastKey = mKeys.get(key.id);
+        if (lastKey != null) {
+            if ((lastKey.stackId != key.stackId) || (lastKey.lastActiveTime < key.lastActiveTime)) {
+                // The task has updated (been made active since the last time it was put into the
+                // LRU cache) or the stack id for the task has changed, invalidate that cache item
+                remove(key);
+                return null;
+            }
+        }
+        // Either the task does not exist in the cache, or the last active time is the same as
+        // the key specified, so return what is in the cache
+        return mCache.get(key.id);
+    }
+
+    /** Puts an entry in the cache for a specific key. */
+    final void put(Task.TaskKey key, V value) {
+        mKeys.put(key.id, key);
+        mCache.put(key.id, value);
+    }
+
+    /** Removes a cache entry for a specific key. */
+    final void remove(Task.TaskKey key) {
+        mKeys.remove(key.id);
+        mCache.remove(key.id);
+    }
+
+    /** Removes all the entries in the cache. */
+    final void evictAll() {
+        mCache.evictAll();
+        mKeys.clear();
+    }
+
+    /** Trims the cache to a specific size */
+    final void trimToSize(int cacheSize) {
+        mCache.resize(cacheSize);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 6598112..b3937c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.model;
 
 import android.animation.ObjectAnimator;
-import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -25,13 +25,16 @@
 import android.graphics.drawable.ColorDrawable;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.NamedCounter;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
 
@@ -424,8 +427,8 @@
         boolean filtered = mTaskList.setFilter(new TaskFilter() {
             @Override
             public boolean acceptTask(Task at, int i) {
-                return t.key.baseIntent.getComponent().getPackageName().equals(
-                        at.key.baseIntent.getComponent().getPackageName());
+                return t.key.getComponent().getPackageName().equals(
+                        at.key.getComponent().getPackageName());
             }
         });
         if (filtered && mCb != null) {
@@ -490,7 +493,7 @@
             int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
-                String packageName = t.key.baseIntent.getComponent().getPackageName();
+                String packageName = t.key.getComponent().getPackageName();
                 packageName = "pkg";
                 TaskGrouping group;
                 if (packageName.equals(prevPackage) && groupCountDown > 0) {
@@ -577,6 +580,37 @@
         }
     }
 
+    /**
+     * Computes the components of tasks in this stack that have been removed as a result of a change
+     * in the specified package.
+     */
+    public HashSet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
+        // Identify all the tasks that should be removed as a result of the package being removed.
+        // Using a set to ensure that we callback once per unique component.
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        HashSet<ComponentName> existingComponents = new HashSet<>();
+        HashSet<ComponentName> removedComponents = new HashSet<>();
+        ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
+        for (Task.TaskKey t : taskKeys) {
+            // Skip if this doesn't apply to the current user
+            if (t.userId != userId) continue;
+
+            ComponentName cn = t.getComponent();
+            if (cn.getPackageName().equals(packageName)) {
+                if (existingComponents.contains(cn)) {
+                    // If we know that the component still exists in the package, then skip
+                    continue;
+                }
+                if (ssp.getActivityInfo(cn, userId) != null) {
+                    existingComponents.add(cn);
+                } else {
+                    removedComponents.add(cn);
+                }
+            }
+        }
+        return removedComponents;
+    }
+
     @Override
     public String toString() {
         String str = "Tasks:\n";
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 a5fd3eb..7f618e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.INVALID_STACK_ID;
-
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -44,22 +42,26 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsAppWidgetHostView;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
 /**
  * This view is the the top level layout that contains TaskStacks (which are laid out according
  * to their SpaceNode bounds.
@@ -75,8 +77,6 @@
         public void onTaskViewClicked();
         public void onTaskLaunchFailed();
         public void onAllTaskViewsDismissed();
-        public void onExitToHomeAnimationTriggered();
-        public void onScreenPinningRequest();
         public void runAfterPause(Runnable r);
     }
 
@@ -261,7 +261,7 @@
         ctx.postAnimationTrigger.decrement();
 
         // Notify of the exit animation
-        mCb.onExitToHomeAnimationTriggered();
+        EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted());
     }
 
     /** Adds the search bar */
@@ -404,14 +404,6 @@
         return super.verifyDrawable(who);
     }
 
-    /** Notifies each task view of the user interaction. */
-    public void onUserInteraction() {
-        // Get the first stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.onUserInteraction();
-        }
-    }
-
     /** Focuses the next task in the first stack view */
     public void focusNextTask(boolean forward) {
         // Get the first stack view
@@ -602,8 +594,8 @@
 
         final int left = pts[0] + offsetX;
         final int top = pts[1] + offsetY;
-        final Rect rect = new Rect(left, top, left + transform.rect.width(),
-                top + transform.rect.height());
+        final Rect rect = new Rect(left, top, left + (int) transform.rect.width(),
+                top + (int) transform.rect.height());
 
         return new AppTransitionAnimationSpec(taskId, b, rect);
     }
@@ -630,15 +622,14 @@
             // and then offset to the expected transform rect, but bound this to just
             // outside the display rect (to ensure we don't animate from too far away)
             sourceView = stackView;
-            offsetX = transform.rect.left;
+            offsetX = (int) transform.rect.left;
             offsetY = getMeasuredHeight();
         } else {
             sourceView = tv.mThumbnailView;
         }
 
         // Compute the thumbnail to scale up from
-        final SystemServicesProxy ssp =
-                RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        final SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityOptions opts = null;
         ActivityOptions.OnAnimationStartedListener animStartedListener = null;
         if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
@@ -652,7 +643,8 @@
                             postDelayed(new Runnable() {
                                 @Override
                                 public void run() {
-                                    mCb.onScreenPinningRequest();
+                                    EventBus.getDefault().send(new ScreenPinningRequestEvent(
+                                            getContext(), ssp));
                                 }
                             }, 350);
                             mTriggered = true;
@@ -664,7 +656,7 @@
                     animStartedListener, destinationStack);
             opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
                     Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
-                    offsetX, offsetY, transform.rect.width(), transform.rect.height(),
+                    offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(),
                     sourceView.getHandler(), animStartedListener);
         } else {
             opts = ActivityOptions.makeBasic();
@@ -684,7 +676,8 @@
                     if (ssp.startActivityFromRecents(getContext(), task.key.id,
                             task.activityLabel, launchOpts)) {
                         if (screenPinningRequested) {
-                            mCb.onScreenPinningRequest();
+                            EventBus.getDefault().send(new ScreenPinningRequestEvent(
+                                    getContext(), ssp));
                         }
                     } else {
                         // Dismiss the task and return the user to home if we fail to
@@ -742,14 +735,6 @@
         MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
     }
 
-    /** Final callback after Recents is finally hidden. */
-    public void onRecentsHidden() {
-        // Notify each task stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.onRecentsHidden();
-        }
-    }
-
     @Override
     public void onTaskStackFilterTriggered() {
         // Hide the search bar
@@ -814,7 +799,7 @@
 
                 // Dock the new task if we are hovering over a valid dock state
                 if (event.dockState != TaskStack.DockState.NONE) {
-                    SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+                    SystemServicesProxy ssp = Recents.getSystemServices();
                     ssp.setTaskResizeable(event.task.key.id);
                     ssp.dockTask(event.task.key.id, event.dockState.createMode);
                     launchTask(event.task, null, INVALID_STACK_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index b28cc21..5fbea00 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -24,6 +24,8 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
 
 /** Manages the scrims for the various system bars. */
 public class SystemBarScrimViews {
@@ -74,10 +76,12 @@
                 View.VISIBLE : View.INVISIBLE);
     }
 
+    /**** EventBus events ****/
+
     /**
      * Starts animating the scrim views when entering Recents.
      */
-    public void startEnterRecentsAnimation() {
+    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
         RecentsActivityLaunchState launchState = mConfig.getLaunchState();
         int transitionEnterFromAppDelay = mContext.getResources().getInteger(
                 R.integer.recents_enter_from_app_transition_duration);
@@ -124,7 +128,7 @@
      * Starts animating the scrim views when leaving Recents (either via launching a task, or
      * going home).
      */
-    public void startExitRecentsAnimation() {
+    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
         int taskViewExitToAppDuration = mContext.getResources().getInteger(
                 R.integer.recents_task_exit_to_app_duration);
         if (mHasStatusBarScrim && mShouldAnimateStatusBarScrim) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index f637407..5928854 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,15 +16,12 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.INVALID_STACK_ID;
-
 import android.animation.ValueAnimator;
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -33,16 +30,18 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
@@ -53,6 +52,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
 
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
@@ -88,11 +89,11 @@
     boolean mStartEnterAnimationRequestedAfterLayout;
     boolean mStartEnterAnimationCompleted;
     ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
+
     Rect mTaskStackBounds = new Rect();
     int[] mTmpVisibleRange = new int[2];
-    float[] mTmpCoord = new float[2];
-    Matrix mTmpMatrix = new Matrix();
     Rect mTmpRect = new Rect();
+    RectF mTmpTaskRect = new RectF();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
     HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
@@ -331,8 +332,7 @@
     /** Synchronizes the views with the model */
     boolean synchronizeStackViewsWithModel() {
         if (mStackViewsDirty) {
-            RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-            SystemServicesProxy ssp = loader.getSystemServicesProxy();
+            SystemServicesProxy ssp = Recents.getSystemServices();
 
             // Get all the task transforms
             ArrayList<Task> tasks = mStack.getTasks();
@@ -412,23 +412,24 @@
         return false;
     }
 
-    /** Updates the clip for each of the task views. */
+    /**
+     * Updates the clip for each of the task views from back to front.
+     */
     void clipTaskViews() {
         // Update the clip on each task child
         List<TaskView> taskViews = getTaskViews();
+        TaskView tmpTv = null;
         int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount - 1; i++) {
+        for (int i = 0; i < taskViewCount; i++) {
             TaskView tv = taskViews.get(i);
-            TaskView nextTv = null;
-            TaskView tmpTv = null;
+            TaskView frontTv = null;
             int clipBottom = 0;
-            if (tv.shouldClipViewInStack()) {
+            if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
                 // Find the next view to clip against
-                int nextIndex = i;
-                while (nextIndex < (taskViewCount - 1)) {
-                    tmpTv = taskViews.get(++nextIndex);
-                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
-                        nextTv = tmpTv;
+                for (int j = i + 1; j < taskViewCount; j++) {
+                    tmpTv = taskViews.get(j);
+                    if (tmpTv.shouldClipViewInStack()) {
+                        frontTv = tmpTv;
                         break;
                     }
                 }
@@ -436,23 +437,23 @@
                 // Clip against the next view, this is just an approximation since we are
                 // stacked and we can make assumptions about the visibility of the this
                 // task relative to the ones in front of it.
-                if (nextTv != null) {
-                    // Map the top edge of next task view into the local space of the current
-                    // task view to find the clip amount in local space
-                    mTmpCoord[0] = mTmpCoord[1] = 0;
-                    Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
-                    Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
-                    clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
-                            - nextTv.getPaddingTop() - 1);
+                if (frontTv != null) {
+                    mTmpTaskRect.set(mLayoutAlgorithm.mTaskRect);
+                    mTmpTaskRect.offset(0, tv.getTranslationY());
+                    Utilities.scaleRectAboutCenter(mTmpTaskRect, tv.getScaleX());
+                    float taskBottom = mTmpTaskRect.bottom;
+                    mTmpTaskRect.set(mLayoutAlgorithm.mTaskRect);
+                    mTmpTaskRect.offset(0, frontTv.getTranslationY());
+                    Utilities.scaleRectAboutCenter(mTmpTaskRect, frontTv.getScaleX());
+                    float frontTaskTop = mTmpTaskRect.top;
+                    if (frontTaskTop < taskBottom) {
+                        // Map the stack view space coordinate (the rects) to view space
+                        clipBottom = (int) ((taskBottom - frontTaskTop) / tv.getScaleX()) - 1;
+                    }
                 }
             }
             tv.getViewBounds().setClipBottom(clipBottom);
         }
-        if (taskViewCount > 0) {
-            // The front most task should never be clipped
-            TaskView tv = taskViews.get(taskViewCount - 1);
-            tv.getViewBounds().setClipBottom(0);
-        }
         mStackViewsClipDirty = false;
     }
 
@@ -877,8 +878,7 @@
                     // Poke the dozer to restart the trigger after the animation completes
                     mUIDozeTrigger.poke();
 
-                    RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-                    SystemServicesProxy ssp = loader.getSystemServicesProxy();
+                    SystemServicesProxy ssp = Recents.getSystemServices();
                     List<TaskView> taskViews = getTaskViews();
                     int taskViewCount = taskViews.size();
                     if (taskViewCount > 0) {
@@ -954,21 +954,10 @@
         }
     }
 
-    /** Final callback after Recents is finally hidden. */
-    void onRecentsHidden() {
-        reset();
-    }
-
     public boolean isTransformedTouchPointInView(float x, float y, View child) {
         return isTransformedTouchPointInView(x, y, child, null);
     }
 
-    /** Pokes the dozer on user interaction. */
-    void onUserInteraction() {
-        // Poke the doze trigger if it is dozing
-        mUIDozeTrigger.poke();
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         mLayersDisabled = false;
@@ -1149,7 +1138,7 @@
         }
 
         // Report that this tasks's data is no longer being used
-        RecentsTaskLoader.getInstance().unloadTaskData(task);
+        Recents.getTaskLoader().unloadTaskData(task);
 
         // Detach the view from the hierarchy
         detachViewFromParent(tv);
@@ -1173,7 +1162,7 @@
         tv.onTaskBound(task);
 
         // Load the task data
-        RecentsTaskLoader.getInstance().loadTaskData(task);
+        Recents.getTaskLoader().loadTaskData(task);
 
         // If the doze trigger has already fired, then update the state for this task view
         tv.setNoUserInteractionState();
@@ -1262,14 +1251,14 @@
 
     public final void onBusEvent(PackagesChangedEvent event) {
         // Compute which components need to be removed
-        HashSet<ComponentName> removedComponents = event.monitor.computeComponentsRemoved(
-                mStack.getTaskKeys(), event.packageName, event.userId);
+        HashSet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
+                event.packageName, event.userId);
 
         // For other tasks, just remove them directly if they no longer exist
         ArrayList<Task> tasks = mStack.getTasks();
         for (int i = tasks.size() - 1; i >= 0; i--) {
             final Task t = tasks.get(i);
-            if (removedComponents.contains(t.key.baseIntent.getComponent())) {
+            if (removedComponents.contains(t.key.getComponent())) {
                 TaskView tv = getChildViewForTask(t);
                 if (tv != null) {
                     // For visible children, defer removing the task until after the animation
@@ -1316,4 +1305,15 @@
             }
         }
     }
+
+    public final void onBusEvent(UserInteractionEvent event) {
+        // Poke the doze trigger on user interaction
+        mUIDozeTrigger.poke();
+    }
+
+    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+        if (!event.visible) {
+            reset();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 4322f1a..1274318 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -26,8 +26,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 
 import java.util.List;
@@ -395,8 +395,7 @@
 
         // The user intentionally tapped on the background, which is like a tap on the "desktop".
         // Hide recents and transition to the launcher.
-        Recents recents = Recents.getInstanceAndStartIfNeeded(mSv.getContext());
-        recents.hideRecents(false /* altTab */, true /* homeKey */);
+        EventBus.getDefault().send(new HideRecentsEvent(false, true));
     }
 
     /** Handles generic motion events */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index d227d55..bab4da7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -47,9 +47,7 @@
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
@@ -761,7 +759,7 @@
     }
 
     @Override
-    public void onMultiStackDebugTaskStackIdChanged() {
+    public void onTaskStackIdChanged() {
         mHeaderView.rebindToTask(mTask);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 949d515..e1e07ef 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -48,13 +48,12 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.ResizeTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 
 
@@ -62,8 +61,6 @@
 public class TaskViewHeader extends FrameLayout
         implements View.OnClickListener, View.OnLongClickListener {
 
-    RecentsConfiguration mConfig;
-    private SystemServicesProxy mSsp;
     Task mTask;
 
     // Header views
@@ -111,8 +108,6 @@
 
     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mConfig = RecentsConfiguration.getInstance();
-        mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
         setWillNotDraw(false);
         setClipToOutline(true);
         setOutlineProvider(new ViewOutlineProvider() {
@@ -266,8 +261,9 @@
 
     /** Updates the resize task bar button. */
     void updateResizeTaskBarIcon(Task t) {
-        Rect display = mSsp.getWindowRect();
-        Rect taskRect = mSsp.getTaskBounds(t.key.id);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Rect display = ssp.getWindowRect();
+        Rect taskRect = ssp.getTaskBounds(t.key.id);
         int resId = R.drawable.star;
         if (display.equals(taskRect) || taskRect.isEmpty()) {
             resId = R.drawable.vector_drawable_place_fullscreen;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index f8f7052..ec50eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.views;
 
 import android.animation.ValueAnimator;
-import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
 import android.view.animation.Interpolator;
@@ -35,7 +35,7 @@
 
     // This is a window-space rect that is purely used for coordinating the animation of an app
     // window into Recents.
-    public Rect rect = new Rect();
+    public RectF rect = new RectF();
 
     public TaskViewTransform() {
         // Do nothing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 687f6c1..7b0a44f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -149,10 +149,7 @@
     }
 
     private boolean isFalseTouch() {
-        if (mFalsingManager.isClassiferEnabled()) {
-            return mFalsingManager.isFalseTouch();
-        }
-        return !mDraggedFarEnough;
+        return mFalsingManager.isFalseTouch() || !mDraggedFarEnough;
     }
 
     private void captureStartingChild(float x, float y) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 210be9f..33f21ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -31,6 +31,7 @@
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.LinearInterpolator;
+import android.widget.Chronometer;
 import android.widget.ImageView;
 
 import com.android.systemui.R;
@@ -88,6 +89,7 @@
     private NotificationGuts mGuts;
     private StatusBarNotification mStatusBarNotification;
     private boolean mIsHeadsUp;
+    private boolean mLastChronometerRunning = true;
     private View mExpandButton;
     private View mExpandButtonDivider;
     private ViewStub mExpandButtonStub;
@@ -295,6 +297,7 @@
      */
     public void setPinned(boolean pinned) {
         mIsPinned = pinned;
+        setChronometerRunning(mLastChronometerRunning);
     }
 
     public boolean isPinned() {
@@ -320,6 +323,41 @@
         return mJustClicked;
     }
 
+    public void setChronometerRunning(boolean running) {
+        mLastChronometerRunning = running;
+        setChronometerRunning(running, mPrivateLayout);
+        setChronometerRunning(running, mPublicLayout);
+        if (mChildrenContainer != null) {
+            List<ExpandableNotificationRow> notificationChildren =
+                    mChildrenContainer.getNotificationChildren();
+            for (int i = 0; i < notificationChildren.size(); i++) {
+                ExpandableNotificationRow child = notificationChildren.get(i);
+                child.setChronometerRunning(running);
+            }
+        }
+    }
+
+    private void setChronometerRunning(boolean running, NotificationContentView layout) {
+        if (layout != null) {
+            running = running || isPinned();
+            View contractedChild = layout.getContractedChild();
+            View expandedChild = layout.getExpandedChild();
+            View headsUpChild = layout.getHeadsUpChild();
+            setChronometerRunningForChild(running, contractedChild);
+            setChronometerRunningForChild(running, expandedChild);
+            setChronometerRunningForChild(running, headsUpChild);
+        }
+    }
+
+    private void setChronometerRunningForChild(boolean running, View child) {
+        if (child != null) {
+            View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
+            if (chronometer instanceof Chronometer) {
+                ((Chronometer) chronometer).setStarted(running);
+            }
+        }
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 76edafa..ebe7785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -229,7 +229,7 @@
         }
 
         // On expanding, single mouse click expands the panel instead of dragging.
-        if (isFullyCollapsed() && event.getDevice().getSources() == InputDevice.SOURCE_MOUSE) {
+        if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             if (event.getAction() == MotionEvent.ACTION_UP) {
                 expand(true);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 540b9d0..fa9c4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -109,6 +109,14 @@
         }
     };
 
+    private Runnable mRemoveCastIconRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW");
+            mService.setIconVisibility(SLOT_CAST, false);
+        }
+    };
+
     public PhoneStatusBarPolicy(Context context, CastController cast, HotspotController hotspot,
             UserInfoController userInfoController, BluetoothController bluetooth) {
         mContext = context;
@@ -328,11 +336,17 @@
             }
         }
         if (DEBUG) Log.v(TAG, "updateCast: isCasting: " + isCasting);
+        mHandler.removeCallbacks(mRemoveCastIconRunnable);
         if (isCasting) {
             mService.setIcon(SLOT_CAST, R.drawable.stat_sys_cast, 0,
                     mContext.getString(R.string.accessibility_casting));
+            mService.setIconVisibility(SLOT_CAST, true);
+        } else {
+            // don't turn off the screen-record icon for a few seconds, just to make sure the user
+            // has seen it
+            if (DEBUG) Log.v(TAG, "updateCast: hiding icon in 3 sec...");
+            mHandler.postDelayed(mRemoveCastIconRunnable, 3000);
         }
-        mService.setIconVisibility(SLOT_CAST, isCasting);
     }
 
     private void profileChanged(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index f8ddc73..96b919e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -246,7 +246,7 @@
         }
     }
 
-    protected QSTile<?> createTile(String tileSpec) {
+    public QSTile<?> createTile(String tileSpec) {
         if (tileSpec.equals("wifi")) return new WifiTile(this, false);
         else if (tileSpec.equals("bt")) return new BluetoothTile(this, false);
         else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
@@ -272,6 +272,7 @@
         else if (tileSpec.equals("qlock")) return new QLockTile(this);
         // Intent tiles.
         else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
+        else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
         else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 01f0667..662dbd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -20,7 +20,9 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Rect;
 import android.graphics.drawable.Animatable;
+import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -28,6 +30,7 @@
 import android.widget.Switch;
 import android.widget.TextView;
 import android.widget.Toast;
+
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
@@ -86,6 +89,20 @@
         mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
         mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
         mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
+
+        // RenderThread is doing more harm than good when touching the header (to expand quick
+        // settings), so disable it for this view
+        ((RippleDrawable) getBackground()).setForceSoftware(true);
+        ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
+
+        addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right,
+                    int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                setClipBounds(new Rect(getPaddingLeft(), 0, getWidth() - getPaddingRight(),
+                        getHeight()));
+            }
+        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index d701b3c..116237d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -30,7 +30,11 @@
     private final Context mContext;
 
     public SystemUIDialog(Context context) {
-        super(context, R.style.Theme_SystemUI_Dialog);
+        this(context, R.style.Theme_SystemUI_Dialog);
+    }
+
+    public SystemUIDialog(Context context, int theme) {
+        super(context, theme);
         mContext = context;
 
         getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 4f7756e..a515f23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -170,7 +170,6 @@
      */
     public void showNotification(NotificationData.Entry headsUp) {
         if (DEBUG) Log.v(TAG, "showNotification");
-        MetricsLogger.count(mContext, "note_peek", 1);
         addHeadsUpEntry(headsUp);
         updateNotification(headsUp, true);
         headsUp.setInterruption();
@@ -247,6 +246,9 @@
             return;
         }
         mHasPinnedNotification = hasPinnedNotification;
+        if (mHasPinnedNotification) {
+            MetricsLogger.count(mContext, "note_peek", 1);
+        }
         updateTouchableRegionListener();
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
@@ -600,6 +602,13 @@
 
         @Override
         public int compareTo(HeadsUpEntry o) {
+            boolean selfFullscreen = hasFullScreenIntent(entry);
+            boolean otherFullscreen = hasFullScreenIntent(o.entry);
+            if (selfFullscreen && !otherFullscreen) {
+                return -1;
+            } else if (!selfFullscreen && otherFullscreen) {
+                return 1;
+            }
             return postTime < o.postTime ? 1
                     : postTime == o.postTime ? entry.key.compareTo(o.entry.key)
                             : -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 4c99792..4d268ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -18,12 +18,14 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
@@ -43,6 +45,7 @@
 
 public class KeyButtonView extends ImageView {
 
+    private int mContentDescriptionRes;
     private long mDownTime;
     private int mCode;
     private int mTouchSlop;
@@ -79,8 +82,14 @@
 
         mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
 
+        TypedValue value = new TypedValue();
+        if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
+            mContentDescriptionRes = value.resourceId;
+        }
+
         a.recycle();
 
+
         setClickable(true);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -88,6 +97,15 @@
     }
 
     @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        if (mContentDescriptionRes != 0) {
+            setContentDescription(mContext.getString(mContentDescriptionRes));
+        }
+    }
+
+    @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         if (mCode != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 5e5f810..d50f8ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1789,6 +1789,7 @@
         ((ExpandableView) child).setOnHeightChangedListener(this);
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
+        updateChronometerForChild(child);
         if (canChildBeDismissed(child)) {
             // Make sure the dismissButton is visible and not in the animated state.
             // We need to do this to avoid a race where a clearable notification is added after the
@@ -2298,6 +2299,21 @@
         mStackScrollAlgorithm.setIsExpanded(isExpanded);
         if (changed) {
             updateNotificationAnimationStates();
+            updateChronometers();
+        }
+    }
+
+    private void updateChronometers() {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            updateChronometerForChild(getChildAt(i));
+        }
+    }
+
+    private void updateChronometerForChild(View child) {
+        if (child instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            row.setChronometerRunning(mIsExpanded);
         }
     }
 
@@ -2320,6 +2336,7 @@
         }
         mStackScrollAlgorithm.onReset(view);
         updateAnimationState(view);
+        updateChronometerForChild(view);
     }
 
     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index 3ac2a94..4ce0933 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -211,7 +211,7 @@
         }
 
         @Override
-        protected QSTile<?> createTile(String tileSpec) {
+        public QSTile<?> createTile(String tileSpec) {
             return new DraggableTile(this, tileSpec);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 02ee5d4..1770a06 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -79,6 +79,7 @@
     private static final int FOREVER_CONDITION_INDEX = 0;
     private static final int COUNTDOWN_CONDITION_INDEX = 1;
     private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
+    private static final int COUNTDOWN_CONDITION_COUNT = 2;
 
     public static final Intent ZEN_SETTINGS
             = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
@@ -89,7 +90,6 @@
     private final LayoutInflater mInflater;
     private final H mHandler = new H();
     private final ZenPrefs mPrefs;
-    private final IconPulser mIconPulser;
     private final TransitionHelper mTransitionHelper = new TransitionHelper();
     private final Uri mForeverId;
     private final SpTexts mSpTexts;
@@ -126,7 +126,6 @@
         mContext = context;
         mPrefs = new ZenPrefs();
         mInflater = LayoutInflater.from(mContext.getApplicationContext());
-        mIconPulser = new IconPulser(mContext);
         mForeverId = Condition.newId(mContext).appendPath("forever").build();
         mSpTexts = new SpTexts(mContext);
         mVoiceCapable = Util.isVoiceCapable(mContext);
@@ -297,10 +296,6 @@
             }
             if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
 
-            mTimeUntilAlarmCondition = parseExistingTimeCondition(mContext, mExitCondition);
-            if (mTimeUntilAlarmCondition == null) {
-                mTimeUntilAlarmCondition = getTimeUntilNextAlarmCondition();
-            }
             mConditions = null; // reset conditions
             handleUpdateConditions();
         } else {
@@ -311,7 +306,7 @@
     public void init(ZenModeController controller) {
         mController = controller;
         mCountdownConditionSupported = mController.isCountdownConditionSupported();
-        final int countdownDelta = mCountdownConditionSupported ? 2 : 0;
+        final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0;
         final int minConditions = 1 /*forever*/ + countdownDelta;
         for (int i = 0; i < minConditions; i++) {
             mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
@@ -489,10 +484,13 @@
                     COUNTDOWN_CONDITION_INDEX);
         }
         // countdown until alarm
-        if (mCountdownConditionSupported && mTimeUntilAlarmCondition != null) {
-            bind(mTimeUntilAlarmCondition,
-                    mZenConditions.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX),
-                    COUNTDOWN_ALARM_CONDITION_INDEX);
+        if (mCountdownConditionSupported) {
+            Condition nextAlarmCondition = getTimeUntilNextAlarmCondition();
+            if (nextAlarmCondition != null) {
+                bind(nextAlarmCondition,
+                        mZenConditions.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX),
+                        COUNTDOWN_ALARM_CONDITION_INDEX);
+            }
         }
         // ensure something is selected
         if (mExpanded && isShown()) {
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index d6d17cb..fdc2543 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -22,7 +22,9 @@
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
 
 LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.systemui:com.android.keyguard
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, src) \
     $(call all-java-files-under, ../src) \
     $(call all-proto-files-under, ../src) \
     src/com/android/systemui/EventLogTags.logtags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
new file mode 120000
index 0000000..0ea3e91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -0,0 +1 @@
+../../../../../../src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
new file mode 120000
index 0000000..b1a0963
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -0,0 +1 @@
+../../../../../../src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
\ No newline at end of file
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 3759c91..be7071e 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -64,6 +64,10 @@
     case RS_TYPE_FLOAT_32:                                                              \
         len = _env->GetArrayLength((jfloatArray)data);                                  \
         ptr = _env->GetFloatArrayElements((jfloatArray)data, flag);                     \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 4;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -89,6 +93,10 @@
     case RS_TYPE_FLOAT_64:                                                              \
         len = _env->GetArrayLength((jdoubleArray)data);                                 \
         ptr = _env->GetDoubleArrayElements((jdoubleArray)data, flag);                   \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 8;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -115,6 +123,10 @@
     case RS_TYPE_UNSIGNED_8:                                                            \
         len = _env->GetArrayLength((jbyteArray)data);                                   \
         ptr = _env->GetByteArrayElements((jbyteArray)data, flag);                       \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 1;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -141,6 +153,10 @@
     case RS_TYPE_UNSIGNED_16:                                                           \
         len = _env->GetArrayLength((jshortArray)data);                                  \
         ptr = _env->GetShortArrayElements((jshortArray)data, flag);                     \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 2;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -167,6 +183,10 @@
     case RS_TYPE_UNSIGNED_32:                                                           \
         len = _env->GetArrayLength((jintArray)data);                                    \
         ptr = _env->GetIntArrayElements((jintArray)data, flag);                         \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 4;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -193,6 +213,10 @@
     case RS_TYPE_UNSIGNED_64:                                                           \
         len = _env->GetArrayLength((jlongArray)data);                                   \
         ptr = _env->GetLongArrayElements((jlongArray)data, flag);                       \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 8;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -332,16 +356,40 @@
 
   jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
   jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
+  if (jFieldIDs == nullptr) {
+      ALOGE("Failed to get Java array elements: fieldIDs.");
+      return ret;
+  }
+
   jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
   jsize values_length = _env->GetArrayLength(valueArray);
+  if (jValues == nullptr) {
+      ALOGE("Failed to get Java array elements: values.");
+      return ret;
+  }
+
   jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
   jsize sizes_length = _env->GetArrayLength(sizeArray);
+  if (jSizes == nullptr) {
+      ALOGE("Failed to get Java array elements: sizes.");
+      return ret;
+  }
+
   jlong* jDepClosures =
       _env->GetLongArrayElements(depClosureArray, nullptr);
   jsize depClosures_length = _env->GetArrayLength(depClosureArray);
+  if (jDepClosures == nullptr) {
+      ALOGE("Failed to get Java array elements: depClosures.");
+      return ret;
+  }
+
   jlong* jDepFieldIDs =
       _env->GetLongArrayElements(depFieldIDArray, nullptr);
   jsize depFieldIDs_length = _env->GetArrayLength(depFieldIDArray);
+  if (jDepFieldIDs == nullptr) {
+      ALOGE("Failed to get Java array elements: depFieldIDs.");
+      return ret;
+  }
 
   size_t numValues, numDependencies;
   RsScriptFieldID* fieldIDs;
@@ -435,12 +483,31 @@
 
   jbyte* jParams = _env->GetByteArrayElements(paramArray, nullptr);
   jsize jParamLength = _env->GetArrayLength(paramArray);
+  if (jParams == nullptr) {
+      ALOGE("Failed to get Java array elements: params.");
+      return ret;
+  }
+
   jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
   jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
+  if (jFieldIDs == nullptr) {
+      ALOGE("Failed to get Java array elements: fieldIDs.");
+      return ret;
+  }
+
   jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
   jsize values_length = _env->GetArrayLength(valueArray);
+  if (jValues == nullptr) {
+      ALOGE("Failed to get Java array elements: values.");
+      return ret;
+  }
+
   jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
   jsize sizes_length = _env->GetArrayLength(sizeArray);
+  if (jSizes == nullptr) {
+      ALOGE("Failed to get Java array elements: sizes.");
+      return ret;
+  }
 
   size_t numValues;
   RsScriptFieldID* fieldIDs;
@@ -515,6 +582,10 @@
 
   jlong* jClosures = _env->GetLongArrayElements(closureArray, nullptr);
   jsize numClosures = _env->GetArrayLength(closureArray);
+  if (jClosures == nullptr) {
+      ALOGE("Failed to get Java array elements: closures.");
+      return ret;
+  }
 
   RsClosure* closures;
 
@@ -720,6 +791,11 @@
     }
     jint len = _env->GetArrayLength(str);
     jbyte * cptr = (jbyte *) _env->GetPrimitiveArrayCritical(str, 0);
+    if (cptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
+
     rsAssignName((RsContext)con, (void *)obj, (const char *)cptr, len);
     _env->ReleasePrimitiveArrayCritical(str, cptr, JNI_ABORT);
 }
@@ -916,6 +992,10 @@
         ALOGD("nContextGetMessage, con(%p), len(%i)", (RsContext)con, len);
     }
     jint *ptr = _env->GetIntArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
     size_t receiveLen;
     uint32_t subID;
     int id = rsContextGetMessage((RsContext)con,
@@ -936,6 +1016,10 @@
         ALOGD("nContextPeekMessage, con(%p)", (RsContext)con);
     }
     jint *auxDataPtr = _env->GetIntArrayElements(auxData, nullptr);
+    if (auxDataPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
     size_t receiveLen;
     uint32_t subID;
     int id = rsContextPeekMessage((RsContext)con, &receiveLen, sizeof(receiveLen),
@@ -970,6 +1054,10 @@
     if (data) {
         len = _env->GetArrayLength(data);
         ptr = _env->GetIntArrayElements(data, nullptr);
+        if (ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
     }
     if (kLogApi) {
         ALOGD("nContextSendMessage, con(%p), id(%i), len(%i)", (RsContext)con, id, len);
@@ -1004,7 +1092,15 @@
     }
 
     jlong *jIds = _env->GetLongArrayElements(_ids, nullptr);
+    if (jIds == nullptr) {
+        ALOGE("Failed to get Java array elements: ids");
+        return 0;
+    }
     jint *jArraySizes = _env->GetIntArrayElements(_arraySizes, nullptr);
+    if (jArraySizes == nullptr) {
+        ALOGE("Failed to get Java array elements: arraySizes");
+        return 0;
+    }
 
     RsElement *ids = (RsElement*)malloc(fieldCount * sizeof(RsElement));
     uint32_t *arraySizes = (uint32_t *)malloc(fieldCount * sizeof(uint32_t));
@@ -1311,6 +1407,10 @@
               sizeBytes);
     }
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsAllocationElementData((RsContext)con, (RsAllocation)alloc,
                             xoff, yoff, zoff,
                             lod, ptr, sizeBytes, compIdx);
@@ -1449,6 +1549,10 @@
               sizeBytes);
     }
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsAllocationElementRead((RsContext)con, (RsAllocation)alloc,
                             xoff, yoff, zoff,
                             lod, ptr, sizeBytes, compIdx);
@@ -1775,6 +1879,10 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptSetVarV((RsContext)con, (RsScript)script, slot, ptr, len);
     _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
 }
@@ -1787,6 +1895,10 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptGetVarV((RsContext)con, (RsScript)script, slot, ptr, len);
     _env->ReleaseByteArrayElements(data, ptr, 0);
 }
@@ -1800,8 +1912,16 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     jint dimsLen = _env->GetArrayLength(dims) * sizeof(int);
     jint *dimsPtr = _env->GetIntArrayElements(dims, nullptr);
+    if (dimsPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptSetVarVE((RsContext)con, (RsScript)script, slot, ptr, len, (RsElement)elem,
                      (const uint32_t*) dimsPtr, dimsLen);
     _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
@@ -1819,6 +1939,10 @@
     jint length = _env->GetArrayLength(timeZone);
     jbyte* timeZone_ptr;
     timeZone_ptr = (jbyte *) _env->GetPrimitiveArrayCritical(timeZone, (jboolean *)0);
+    if (timeZone_ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
 
     rsScriptSetTimeZone((RsContext)con, (RsScript)script, (const char *)timeZone_ptr, length);
 
@@ -1844,6 +1968,10 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptInvokeV((RsContext)con, (RsScript)script, slot, ptr, len);
     _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
 }
@@ -1870,8 +1998,12 @@
             return;
         }
 
-        // TODO (b/20760800): Check in_ptr is not null
         in_ptr = _env->GetLongArrayElements(ains, nullptr);
+        if (in_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
+
         if (sizeof(RsAllocation) == sizeof(jlong)) {
             in_allocs = (RsAllocation*)in_ptr;
 
@@ -1897,6 +2029,10 @@
     if (params != nullptr) {
         param_len = _env->GetArrayLength(params);
         param_ptr = _env->GetByteArrayElements(params, nullptr);
+        if (param_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
     }
 
     RsScriptCall sc, *sca = nullptr;
@@ -1908,6 +2044,10 @@
     if (limits != nullptr) {
         limit_len = _env->GetArrayLength(limits);
         limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+        if (limit_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
 
         assert(limit_len == 6);
         UNUSED(limit_len);  // As the assert might not be compiled.
@@ -1966,6 +2106,10 @@
     if (limits != nullptr) {
         limit_len = _env->GetArrayLength(limits);
         limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+        if (limit_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
 
         // We expect to be passed an array [x1, x2] which specifies
         // the sub-range for a 1-dimensional reduction.
@@ -2037,6 +2181,10 @@
     }
     script_ptr = (jbyte *)
         _env->GetPrimitiveArrayCritical(scriptRef, (jboolean *)0);
+    if (script_ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return ret;
+    }
 
     //rsScriptCSetText((RsContext)con, (const char *)script_ptr, length);
 
@@ -2104,6 +2252,10 @@
 
     jint kernelsLen = _env->GetArrayLength(_kernels);
     jlong *jKernelsPtr = _env->GetLongArrayElements(_kernels, nullptr);
+    if (jKernelsPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: kernels");
+        return 0;
+    }
     RsScriptKernelID* kernelsPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * kernelsLen);
     for(int i = 0; i < kernelsLen; ++i) {
         kernelsPtr[i] = (RsScriptKernelID)jKernelsPtr[i];
@@ -2111,6 +2263,10 @@
 
     jint srcLen = _env->GetArrayLength(_src);
     jlong *jSrcPtr = _env->GetLongArrayElements(_src, nullptr);
+    if (jSrcPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: src");
+        return 0;
+    }
     RsScriptKernelID* srcPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * srcLen);
     for(int i = 0; i < srcLen; ++i) {
         srcPtr[i] = (RsScriptKernelID)jSrcPtr[i];
@@ -2118,6 +2274,10 @@
 
     jint dstkLen = _env->GetArrayLength(_dstk);
     jlong *jDstkPtr = _env->GetLongArrayElements(_dstk, nullptr);
+    if (jDstkPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: dstk");
+        return 0;
+    }
     RsScriptKernelID* dstkPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstkLen);
     for(int i = 0; i < dstkLen; ++i) {
         dstkPtr[i] = (RsScriptKernelID)jDstkPtr[i];
@@ -2125,6 +2285,10 @@
 
     jint dstfLen = _env->GetArrayLength(_dstf);
     jlong *jDstfPtr = _env->GetLongArrayElements(_dstf, nullptr);
+    if (jDstfPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: dstf");
+        return 0;
+    }
     RsScriptKernelID* dstfPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstfLen);
     for(int i = 0; i < dstfLen; ++i) {
         dstfPtr[i] = (RsScriptKernelID)jDstfPtr[i];
@@ -2132,6 +2296,10 @@
 
     jint typesLen = _env->GetArrayLength(_types);
     jlong *jTypesPtr = _env->GetLongArrayElements(_types, nullptr);
+    if (jTypesPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: types");
+        return 0;
+    }
     RsType* typesPtr = (RsType*) malloc(sizeof(RsType) * typesLen);
     for(int i = 0; i < typesLen; ++i) {
         typesPtr[i] = (RsType)jTypesPtr[i];
@@ -2244,6 +2412,10 @@
     AutoJavaStringToUTF8 shaderUTF(_env, shader);
     jlong *jParamPtr = _env->GetLongArrayElements(params, nullptr);
     jint paramLen = _env->GetArrayLength(params);
+    if (jParamPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
 
     int texCount = _env->GetArrayLength(texNames);
     AutoJavaStringArrayToUTF8 names(_env, texNames, texCount);
@@ -2277,6 +2449,10 @@
     AutoJavaStringToUTF8 shaderUTF(_env, shader);
     jlong *jParamPtr = _env->GetLongArrayElements(params, nullptr);
     jint paramLen = _env->GetArrayLength(params);
+    if (jParamPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
 
     if (kLogApi) {
         ALOGD("nProgramVertexCreate, con(%p), paramLen(%i)", (RsContext)con, paramLen);
@@ -2392,6 +2568,10 @@
 
     jint vtxLen = _env->GetArrayLength(_vtx);
     jlong *jVtxPtr = _env->GetLongArrayElements(_vtx, nullptr);
+    if (jVtxPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: vtx");
+        return 0;
+    }
     RsAllocation* vtxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * vtxLen);
     for(int i = 0; i < vtxLen; ++i) {
         vtxPtr[i] = (RsAllocation)(uintptr_t)jVtxPtr[i];
@@ -2399,6 +2579,10 @@
 
     jint idxLen = _env->GetArrayLength(_idx);
     jlong *jIdxPtr = _env->GetLongArrayElements(_idx, nullptr);
+    if (jIdxPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: idx");
+        return 0;
+    }
     RsAllocation* idxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * idxLen);
     for(int i = 0; i < idxLen; ++i) {
         idxPtr[i] = (RsAllocation)(uintptr_t)jIdxPtr[i];
@@ -2406,6 +2590,10 @@
 
     jint primLen = _env->GetArrayLength(_prim);
     jint *primPtr = _env->GetIntArrayElements(_prim, nullptr);
+    if (primPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: prim");
+        return 0;
+    }
 
     jlong id = (jlong)(uintptr_t)rsMeshCreate((RsContext)con,
                                (RsAllocation *)vtxPtr, vtxLen,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index 7dff1a8..28121b4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -17,53 +17,146 @@
 package com.android.server.accessibility;
 
 import android.content.Context;
+import android.gesture.Gesture;
+import android.gesture.GestureLibraries;
+import android.gesture.GestureLibrary;
+import android.gesture.GesturePoint;
+import android.gesture.GestureStore;
+import android.gesture.GestureStroke;
+import android.gesture.Prediction;
+import android.util.Slog;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
 /**
  * This class handles gesture detection for the Touch Explorer.  It collects
  * touch events, and sends events to mListener as gestures are recognized.
  */
 class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
-    private final GestureDetector mGestureDetector;
+
+    private static final boolean DEBUG = false;
+
+    // Tag for logging received events.
+    private static final String LOG_TAG = "AccessibilityGestureDetector";
+
+    public interface Listener {
+        public void onDoubleTapAndHold(MotionEvent event, int policyFlags);
+        public boolean onDoubleTap(MotionEvent event, int policyFlags);
+        public boolean onGesture(int gestureId);
+    }
+
     private final Listener mListener;
+    private final GestureDetector mGestureDetector;
+
+    // The library for gesture detection.
+    private final GestureLibrary mGestureLibrary;
+
+    // Indicates that a single tap has occurred.
     private boolean mFirstTapDetected;
+
+    // Indicates that the down event of a double tap has occured.
     private boolean mDoubleTapDetected;
+
+    // Indicates that motion events are being collected to match a gesture.
+    private boolean mRecognizingGesture;
+
+    // Policy flags of the previous event.
     private int mPolicyFlags;
 
+    // The X of the previous event.
+    private float mPreviousX;
+
+    // The Y of the previous event.
+    private float mPreviousY;
+
+    // Buffer for storing points for gesture detection.
+    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+    // The minimal delta between moves to add a gesture point.
+    private static final int TOUCH_TOLERANCE = 3;
+
+    // The minimal score for accepting a predicted gesture.
+    private static final float MIN_PREDICTION_SCORE = 2.0f;
+
     AccessibilityGestureDetector(Context context, Listener listener) {
         mListener = listener;
 
         mGestureDetector = new GestureDetector(context, this);
         mGestureDetector.setOnDoubleTapListener(this);
+
+        mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
+        mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */);
+        mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
+        mGestureLibrary.load();
     }
 
-    public void onMotionEvent(MotionEvent event, int policyFlags) {
+    public boolean onMotionEvent(MotionEvent event, int policyFlags) {
+        final float x = event.getX();
+        final float y = event.getY();
+
         mPolicyFlags = policyFlags;
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
                 mDoubleTapDetected = false;
+                mRecognizingGesture = true;
+                mPreviousX = x;
+                mPreviousY = y;
+                mStrokeBuffer.clear();
+                mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (mRecognizingGesture) {
+                    final float dX = Math.abs(x - mPreviousX);
+                    final float dY = Math.abs(y - mPreviousY);
+                    if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
+                        mPreviousX = x;
+                        mPreviousY = y;
+                        mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+                    }
+                }
                 break;
 
             case MotionEvent.ACTION_UP:
-                maybeFinishDoubleTap(event, policyFlags);
+                if (maybeFinishDoubleTap(event, policyFlags)) {
+                    return true;
+                }
+                if (mRecognizingGesture) {
+                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+
+                    if (recognizeGesture()) {
+                        return true;
+                    }
+                }
                 break;
         }
-        mGestureDetector.onTouchEvent(event);
+
+        if (!mRecognizingGesture) {
+            return false;
+        }
+
+        // Pass the event on to the standard gesture detector.
+        return mGestureDetector.onTouchEvent(event);
     }
 
     public void clear() {
         mFirstTapDetected = false;
         mDoubleTapDetected = false;
+        cancelGesture();
+        mStrokeBuffer.clear();
     }
 
     public boolean firstTapDetected() {
         return mFirstTapDetected;
     }
 
-    @Override
-    public boolean onDown(MotionEvent event) {
-        return true;
+    public void cancelGesture() {
+        mRecognizingGesture = false;
+        mStrokeBuffer.clear();
     }
 
     @Override
@@ -101,18 +194,39 @@
         mListener.onDoubleTapAndHold(event, policyFlags);
     }
 
-    private void maybeFinishDoubleTap(MotionEvent event, int policyFlags) {
+    private boolean maybeFinishDoubleTap(MotionEvent event, int policyFlags) {
         if (!mDoubleTapDetected) {
-            return;
+            return false;
         }
 
         clear();
 
-        mListener.onDoubleTap(event, policyFlags);
+        return mListener.onDoubleTap(event, policyFlags);
     }
 
-    public interface Listener {
-        public void onDoubleTapAndHold(MotionEvent event, int policyFlags);
-        public void onDoubleTap(MotionEvent event, int policyFlags);
+    private boolean recognizeGesture() {
+        Gesture gesture = new Gesture();
+        gesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+        ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
+        if (!predictions.isEmpty()) {
+            Prediction bestPrediction = predictions.get(0);
+            if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
+                            + bestPrediction.score);
+                }
+                try {
+                    final int gestureId = Integer.parseInt(bestPrediction.name);
+                    if (mListener.onGesture(gestureId)) {
+                        return true;
+                    }
+                } catch (NumberFormatException nfe) {
+                    Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
+                }
+            }
+        }
+
+        return false;
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 954536f..ca30349 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -17,13 +17,6 @@
 package com.android.server.accessibility;
 
 import android.content.Context;
-import android.gesture.Gesture;
-import android.gesture.GestureLibraries;
-import android.gesture.GestureLibrary;
-import android.gesture.GesturePoint;
-import android.gesture.GestureStore;
-import android.gesture.GestureStroke;
-import android.gesture.Prediction;
 import android.graphics.Point;
 import android.os.Handler;
 import android.util.Slog;
@@ -38,8 +31,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.internal.R;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -167,24 +158,6 @@
     // Context in which this explorer operates.
     private final Context mContext;
 
-    // The X of the previous event.
-    private float mPreviousX;
-
-    // The Y of the previous event.
-    private float mPreviousY;
-
-    // Buffer for storing points for gesture detection.
-    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
-    // The minimal delta between moves to add a gesture point.
-    private static final int TOUCH_TOLERANCE = 3;
-
-    // The minimal score for accepting a predicted gesture.
-    private static final float MIN_PREDICTION_SCORE = 2.0f;
-
-    // The library for gesture detection.
-    private GestureLibrary mGestureLibrary;
-
     // The long pressing pointer id if coordinate remapping is needed.
     private int mLongPressingPointerId = -1;
 
@@ -215,10 +188,6 @@
         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
         mHandler = new Handler(context.getMainLooper());
         mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
-        mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
-        mGestureLibrary.setOrientationStyle(8);
-        mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
-        mGestureLibrary.load();
         mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed();
         mSendHoverExitDelayed = new SendHoverExitDelayed();
         mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed(
@@ -274,8 +243,7 @@
                 sendUpForInjectedDownPointers(event, policyFlags);
             } break;
             case STATE_GESTURE_DETECTING: {
-                // Clear the current stroke.
-                mStrokeBuffer.clear();
+                // No state specific cleanup required.
             } break;
         }
         // Remove all pending callbacks.
@@ -321,6 +289,11 @@
 
         mReceivedPointerTracker.onMotionEvent(rawEvent);
 
+        if (mGestureDetector.onMotionEvent(event, policyFlags)) {
+            // Event was handled by the gesture detector.
+            return;
+        }
+
         switch(mCurrentState) {
             case STATE_TOUCH_EXPLORING: {
                 handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
@@ -389,6 +362,11 @@
 
     @Override
     public void onDoubleTapAndHold(MotionEvent event, int policyFlags) {
+        // Ignore the event if we aren't touch exploring.
+        if (mCurrentState != STATE_TOUCH_EXPLORING) {
+            return;
+        }
+
         // Pointers should not be zero when running this command.
         if (mReceivedPointerTracker.getLastReceivedEvent().getPointerCount() == 0) {
             return;
@@ -415,10 +393,10 @@
     }
 
     @Override
-    public void onDoubleTap(MotionEvent event, int policyFlags) {
-        // This should never be called when more than two pointers are down.
-        if (event.getPointerCount() > 2) {
-            return;
+    public boolean onDoubleTap(MotionEvent event, int policyFlags) {
+        // Ignore the event if we aren't touch exploring.
+        if (mCurrentState != STATE_TOUCH_EXPLORING) {
+            return false;
         }
 
         // Remove pending event deliveries.
@@ -438,7 +416,9 @@
         Point clickLocation = mTempPoint;
         final int result = computeClickLocation(clickLocation);
         if (result == CLICK_LOCATION_NONE) {
-            return;
+            // We can't send a click to no location, but the gesture was still
+            // consumed.
+            return true;
         }
 
         // Do the click.
@@ -456,6 +436,28 @@
         final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
         sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus);
         click_event.recycle();
+        return true;
+    }
+
+    @Override
+    public boolean onGesture(int gestureId) {
+        if (mCurrentState != STATE_GESTURE_DETECTING) {
+            return false;
+        }
+
+        mAms.onTouchInteractionEnd();
+
+        // Announce the end of the gesture recognition.
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+        // Announce the end of a the touch interaction.
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+        mAms.onGesture(gestureId);
+
+        mExitGestureDetectionModeDelayed.cancel();
+        mCurrentState = STATE_TOUCH_EXPLORING;
+
+        return true;
     }
 
     /**
@@ -471,17 +473,10 @@
 
         mVelocityTracker.addMovement(rawEvent);
 
-        mGestureDetector.onMotionEvent(event, policyFlags);
-
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 mAms.onTouchInteractionStart();
 
-                // Pre-feed the motion events to the gesture detector since we
-                // have a distance slop before getting into gesture detection
-                // mode and not using the points within this slop significantly
-                // decreases the quality of gesture recognition.
-                handleMotionEventGestureDetecting(rawEvent, policyFlags);
                 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
 
                 // If we still have not notified the user for the last
@@ -528,12 +523,6 @@
                         // We have not started sending events since we try to
                         // figure out what the user is doing.
                         if (mSendHoverEnterAndMoveDelayed.isPending()) {
-                            // Pre-feed the motion events to the gesture detector since we
-                            // have a distance slop before getting into gesture detection
-                            // mode and not using the points within this slop significantly
-                            // decreases the quality of gesture recognition.
-                            handleMotionEventGestureDetecting(rawEvent, policyFlags);
-
                             // Cache the event until we discern exploration from gesturing.
                             mSendHoverEnterAndMoveDelayed.addEvent(event);
 
@@ -568,6 +557,7 @@
                                 } else {
                                     // We have just decided that the user is touch,
                                     // exploring so start sending events.
+                                    mGestureDetector.cancelGesture();
                                     mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
                                     mSendHoverExitDelayed.cancel();
                                     sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
@@ -613,9 +603,9 @@
                         }
 
                         // We know that a new state transition is to happen and the
-                        // new state will not be gesture recognition, so clear the
-                        // stashed gesture strokes.
-                        mStrokeBuffer.clear();
+                        // new state will not be gesture recognition, so cancel
+                        // the gesture.
+                        mGestureDetector.cancelGesture();
 
                         if (isDraggingGesture(event)) {
                             // Two pointers moving in the same direction within
@@ -655,9 +645,6 @@
             } break;
             case MotionEvent.ACTION_UP: {
                 mAms.onTouchInteractionEnd();
-                // We know that we do not need the pre-fed gesture points are not
-                // needed anymore since the last pointer just went up.
-                mStrokeBuffer.clear();
                 final int pointerId = event.getPointerId(event.getActionIndex());
                 final int pointerIdBits = (1 << pointerId);
 
@@ -820,24 +807,6 @@
 
     private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
         switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
-                final float x = event.getX();
-                final float y = event.getY();
-                mPreviousX = x;
-                mPreviousY = y;
-                mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
-            } break;
-            case MotionEvent.ACTION_MOVE: {
-                final float x = event.getX();
-                final float y = event.getY();
-                final float dX = Math.abs(x - mPreviousX);
-                final float dY = Math.abs(y - mPreviousY);
-                if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
-                    mPreviousX = x;
-                    mPreviousY = y;
-                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
-                }
-            } break;
             case MotionEvent.ACTION_UP: {
                 mAms.onTouchInteractionEnd();
                 // Announce the end of the gesture recognition.
@@ -845,31 +814,6 @@
                 // Announce the end of a the touch interaction.
                 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
-                float x = event.getX();
-                float y = event.getY();
-                mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
-
-                Gesture gesture = new Gesture();
-                gesture.addStroke(new GestureStroke(mStrokeBuffer));
-
-                ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
-                if (!predictions.isEmpty()) {
-                    Prediction bestPrediction = predictions.get(0);
-                    if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
-                        if (DEBUG) {
-                            Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
-                                    + bestPrediction.score);
-                        }
-                        try {
-                            final int gestureId = Integer.parseInt(bestPrediction.name);
-                            mAms.onGesture(gestureId);
-                        } catch (NumberFormatException nfe) {
-                            Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
-                        }
-                    }
-                }
-
-                mStrokeBuffer.clear();
                 mExitGestureDetectionModeDelayed.cancel();
                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 882899e..08f0952 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -971,8 +971,8 @@
             // This is a special alarm that will put the system into idle until it goes off.
             // The caller has given the time they want this to happen at, however we need
             // to pull that earlier if there are existing alarms that have requested to
-            // bring us out of idle.
-            if (mNextWakeFromIdle != null) {
+            // bring us out of idle at an earlier time.
+            if (mNextWakeFromIdle != null && a.whenElapsed > mNextWakeFromIdle.whenElapsed) {
                 a.when = a.whenElapsed = a.maxWhenElapsed = mNextWakeFromIdle.whenElapsed;
             }
             // Add fuzz to make the alarm go off some time before the actual desired time.
@@ -1256,7 +1256,7 @@
                 pw.print("      Idling until: ");
                 if (mPendingIdleUntil != null) {
                     pw.println(mPendingIdleUntil);
-                    mPendingIdleUntil.dump(pw, "    ", nowRTC, nowELAPSED, sdf);
+                    mPendingIdleUntil.dump(pw, "        ", nowRTC, nowELAPSED, sdf);
                 } else {
                     pw.println("null");
                 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d031165..c712a56 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1450,10 +1450,7 @@
     }
 
     private void enforceChangePermission() {
-        int uid = Binder.getCallingUid();
-        Settings.checkAndNoteChangeNetworkStateOperation(mContext, uid, Settings
-                .getPackageNameForUid(mContext, uid), true);
-
+        ConnectivityManager.enforceChangePermission(mContext);
     }
 
     private void enforceTetherAccessPermission() {
@@ -3366,7 +3363,7 @@
                     .setPriority(highPriority ?
                             Notification.PRIORITY_HIGH :
                             Notification.PRIORITY_DEFAULT)
-                    .setDefaults(Notification.DEFAULT_ALL)
+                    .setDefaults(highPriority ? Notification.DEFAULT_ALL : 0)
                     .setOnlyAlertOnce(true)
                     .build();
 
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 87b490d..496e4df 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -109,6 +109,9 @@
     private static final String ACTION_STEP_IDLE_STATE =
             "com.android.server.device_idle.STEP_IDLE_STATE";
 
+    private static final String ACTION_STEP_LIGHT_IDLE_STATE =
+            "com.android.server.device_idle.STEP_LIGHT_IDLE_STATE";
+
     private AlarmManager mAlarmManager;
     private IBatteryStats mBatteryStats;
     private PowerManagerInternal mLocalPowerManager;
@@ -118,16 +121,16 @@
     private Sensor mMotionSensor;
     private LocationManager mLocationManager;
     private LocationRequest mLocationRequest;
-    private PendingIntent mSensingAlarmIntent;
     private PendingIntent mAlarmIntent;
+    private PendingIntent mLightAlarmIntent;
     private Intent mIdleIntent;
+    private Intent mLightIdleIntent;
     private Display mCurDisplay;
     private AnyMotionDetector mAnyMotionDetector;
     private boolean mEnabled;
     private boolean mForceIdle;
     private boolean mScreenOn;
     private boolean mCharging;
-    private boolean mSensing;
     private boolean mNotMoving;
     private boolean mLocating;
     private boolean mLocated;
@@ -138,7 +141,7 @@
 
     /** Device is currently active. */
     private static final int STATE_ACTIVE = 0;
-    /** Device is inactve (screen off, no motion) and we are waiting to for idle. */
+    /** Device is inactive (screen off, no motion) and we are waiting to for idle. */
     private static final int STATE_INACTIVE = 1;
     /** Device is past the initial inactive period, and waiting for the next idle period. */
     private static final int STATE_IDLE_PENDING = 2;
@@ -163,12 +166,35 @@
         }
     }
 
+    /** Device is currently active. */
+    private static final int LIGHT_STATE_ACTIVE = 0;
+    /** Device is inactive (screen off) and we are waiting to for the first light idle. */
+    private static final int LIGHT_STATE_INACTIVE = 1;
+    /** Device is in the light idle state, trying to stay asleep as much as possible. */
+    private static final int LIGHT_STATE_IDLE = 2;
+    /** Device is in the light idle state, but temporarily out of idle to do regular maintenance. */
+    private static final int LIGHT_STATE_IDLE_MAINTENANCE = 3;
+    /** Device light idle state is overriden, now applying full doze state. */
+    private static final int LIGHT_STATE_OVERRIDE = 4;
+    private static String lightStateToString(int state) {
+        switch (state) {
+            case LIGHT_STATE_ACTIVE: return "ACTIVE";
+            case LIGHT_STATE_INACTIVE: return "INACTIVE";
+            case LIGHT_STATE_IDLE: return "IDLE";
+            case LIGHT_STATE_IDLE_MAINTENANCE: return "IDLE_MAINTENANCE";
+            case LIGHT_STATE_OVERRIDE: return "OVERRIDE";
+            default: return Integer.toString(state);
+        }
+    }
+
     private int mState;
+    private int mLightState;
 
     private long mInactiveTimeout;
     private long mNextAlarmTime;
     private long mNextIdlePendingDelay;
     private long mNextIdleDelay;
+    private long mNextLightAlarmTime;
 
     public final AtomicFile mConfigFile;
 
@@ -250,10 +276,14 @@
                 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                     Uri data = intent.getData();
                     String ssp;
-                    if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
                         removePowerSaveWhitelistAppInternal(ssp);
                     }
                 }
+            } else if (ACTION_STEP_LIGHT_IDLE_STATE.equals(intent.getAction())) {
+                synchronized (DeviceIdleController.this) {
+                    stepLightIdleStateLocked();
+                }
             } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
                 synchronized (DeviceIdleController.this) {
                     stepIdleStateLocked();
@@ -305,7 +335,7 @@
         public void onAccuracyChanged(Sensor sensor, int accuracy) {}
 
         public boolean registerLocked() {
-            boolean success = false;
+            boolean success;
             if (mMotionSensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
                 success = mSensorManager.requestTriggerSensor(mMotionListener, mMotionSensor);
             } else {
@@ -380,6 +410,8 @@
      */
     private final class Constants extends ContentObserver {
         // Key names stored in the settings value.
+        private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to";
+        private static final String KEY_LIGHT_IDLE_PENDING_TIMEOUT = "light_idle_pending_to";
         private static final String KEY_INACTIVE_TIMEOUT = "inactive_to";
         private static final String KEY_SENSING_TIMEOUT = "sensing_to";
         private static final String KEY_LOCATING_TIMEOUT = "locating_to";
@@ -401,6 +433,22 @@
                 "sms_temp_app_whitelist_duration";
 
         /**
+         * This is the time, after becoming inactive, that we will start going
+         * in to light-weight idle mode.
+         * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+         * @see #KEY_LIGHT_IDLE_TIMEOUT
+         */
+        public long LIGHT_IDLE_TIMEOUT;
+
+        /**
+         * This is the initial time, after light idle idle, that we will will sit in the
+         * LIGHT_IDLE_MAINTENANCE period for the system to run normally before returning to idle.
+         * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+         * @see #KEY_LIGHT_IDLE_PENDING_TIMEOUT
+         */
+        public long LIGHT_IDLE_PENDING_TIMEOUT;
+
+        /**
          * This is the time, after becoming inactive, at which we start looking at the
          * motion sensor to determine if the device is being left alone.  We don't do this
          * immediately after going inactive just because we don't want to be continually running
@@ -455,7 +503,8 @@
 
         /**
          * This is the initial time, after being idle, that we will allow ourself to be back
-         * in the IDLE_PENDING state allowing the system to run normally until we return to idle.
+         * in the IDLE_MAINTENANCE state allowing the system to run normally until we return to
+         * idle.
          * @see Settings.Global#DEVICE_IDLE_CONSTANTS
          * @see #KEY_IDLE_PENDING_TIMEOUT
          */
@@ -555,6 +604,10 @@
                     Slog.e(TAG, "Bad device idle settings", e);
                 }
 
+                LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT,
+                        !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L);
+                LIGHT_IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_PENDING_TIMEOUT,
+                        !COMPRESS_TIME ? 1 * 60 * 1000L : 15 * 1000L);
                 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
                 SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -592,6 +645,14 @@
         void dump(PrintWriter pw) {
             pw.println("  Settings:");
 
+            pw.print("    "); pw.print(KEY_LIGHT_IDLE_TIMEOUT); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw);
+            pw.println();
+
+            pw.print("    "); pw.print(KEY_LIGHT_IDLE_PENDING_TIMEOUT); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_IDLE_PENDING_TIMEOUT, pw);
+            pw.println();
+
             pw.print("    "); pw.print(KEY_INACTIVE_TIMEOUT); pw.print("=");
             TimeUtils.formatDuration(INACTIVE_TIMEOUT, pw);
             pw.println();
@@ -689,9 +750,10 @@
 
     static final int MSG_WRITE_CONFIG = 1;
     static final int MSG_REPORT_IDLE_ON = 2;
-    static final int MSG_REPORT_IDLE_OFF = 3;
-    static final int MSG_REPORT_ACTIVE = 4;
-    static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 5;
+    static final int MSG_REPORT_IDLE_ON_LIGHT = 3;
+    static final int MSG_REPORT_IDLE_OFF = 4;
+    static final int MSG_REPORT_ACTIVE = 5;
+    static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
 
     final class MyHandler extends Handler {
         MyHandler(Looper looper) {
@@ -704,43 +766,70 @@
                 case MSG_WRITE_CONFIG: {
                     handleWriteConfigFile();
                 } break;
-                case MSG_REPORT_IDLE_ON: {
+                case MSG_REPORT_IDLE_ON:
+                case MSG_REPORT_IDLE_ON_LIGHT: {
                     EventLogTags.writeDeviceIdleOnStart();
-                    mLocalPowerManager.setDeviceIdleMode(true);
+                    final boolean fullChanged;
+                    final boolean lightChanged;
+                    if (msg.what == MSG_REPORT_IDLE_ON) {
+                        fullChanged = mLocalPowerManager.setDeviceIdleMode(true);
+                        lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
+                    } else {
+                        fullChanged = mLocalPowerManager.setDeviceIdleMode(false);
+                        lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
+                    }
                     try {
                         mNetworkPolicyManager.setDeviceIdleMode(true);
-                        mBatteryStats.noteDeviceIdleMode(true, null, Process.myUid());
+                        mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
+                                ? BatteryStats.DEVICE_IDLE_MODE_FULL
+                                : BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
                     } catch (RemoteException e) {
                     }
-                    getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                    if (fullChanged) {
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                    }
+                    if (lightChanged) {
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                    }
                     EventLogTags.writeDeviceIdleOnComplete();
                 } break;
                 case MSG_REPORT_IDLE_OFF: {
                     EventLogTags.writeDeviceIdleOffStart("unknown");
-                    mLocalPowerManager.setDeviceIdleMode(false);
+                    final boolean fullChanged = mLocalPowerManager.setDeviceIdleMode(false);
+                    final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                     try {
                         mNetworkPolicyManager.setDeviceIdleMode(false);
-                        mBatteryStats.noteDeviceIdleMode(false, null, Process.myUid());
+                        mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
+                                null, Process.myUid());
                     } catch (RemoteException e) {
                     }
-                    getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                    if (fullChanged) {
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                    }
+                    if (lightChanged) {
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                    }
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
                 case MSG_REPORT_ACTIVE: {
                     String activeReason = (String)msg.obj;
                     int activeUid = msg.arg1;
-                    boolean needBroadcast = msg.arg2 != 0;
                     EventLogTags.writeDeviceIdleOffStart(
                             activeReason != null ? activeReason : "unknown");
-                    mLocalPowerManager.setDeviceIdleMode(false);
+                    final boolean fullChanged = mLocalPowerManager.setDeviceIdleMode(false);
+                    final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                     try {
                         mNetworkPolicyManager.setDeviceIdleMode(false);
-                        mBatteryStats.noteDeviceIdleMode(false, activeReason, activeUid);
+                        mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
+                                activeReason, activeUid);
                     } catch (RemoteException e) {
                     }
-                    if (needBroadcast) {
+                    if (fullChanged) {
                         getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                     }
+                    if (lightChanged) {
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                    }
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
                 case MSG_TEMP_APP_WHITELIST_TIMEOUT: {
@@ -909,6 +998,7 @@
             // a battery update the next time the level drops.
             mCharging = true;
             mState = STATE_ACTIVE;
+            mLightState = LIGHT_STATE_ACTIVE;
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
         }
 
@@ -967,18 +1057,22 @@
                         .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                 mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
 
-                Intent intentSensing = new Intent(ACTION_STEP_IDLE_STATE)
+                Intent intentLight = new Intent(ACTION_STEP_LIGHT_IDLE_STATE)
                         .setPackage("android")
                         .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                mSensingAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intentSensing, 0);
+                mLightAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intentLight, 0);
 
                 mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
                 mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
+                mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
+                mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
 
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
                 filter.addAction(ACTION_STEP_IDLE_STATE);
+                filter.addAction(ACTION_STEP_LIGHT_IDLE_STATE);
                 getContext().registerReceiver(mReceiver, filter);
                 filter = new IntentFilter();
                 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -1283,32 +1377,43 @@
     }
 
     void scheduleReportActiveLocked(String activeReason, int activeUid) {
-        Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid,
-                mState == STATE_IDLE ? 1 : 0, activeReason);
+        Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
         mHandler.sendMessage(msg);
     }
 
     void becomeActiveLocked(String activeReason, int activeUid) {
         if (DEBUG) Slog.i(TAG, "becomeActiveLocked, reason = " + activeReason);
-        if (mState != STATE_ACTIVE) {
+        if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
             EventLogTags.writeDeviceIdle(STATE_ACTIVE, activeReason);
+            EventLogTags.writeDeviceIdleLight(LIGHT_STATE_ACTIVE, activeReason);
             scheduleReportActiveLocked(activeReason, activeUid);
             mState = STATE_ACTIVE;
+            mLightState = LIGHT_STATE_ACTIVE;
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
             resetIdleManagementLocked();
+            resetLightIdleManagementLocked();
         }
     }
 
     void becomeInactiveIfAppropriateLocked() {
         if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
-        if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) {
+        if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled) {
             // Screen has turned off; we are now going to become inactive and start
             // waiting to see if we will ultimately go idle.
-            mState = STATE_INACTIVE;
-            if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
-            resetIdleManagementLocked();
-            scheduleAlarmLocked(mInactiveTimeout, false);
-            EventLogTags.writeDeviceIdle(mState, "no activity");
+            if (mState == STATE_ACTIVE) {
+                mState = STATE_INACTIVE;
+                if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
+                resetIdleManagementLocked();
+                scheduleAlarmLocked(mInactiveTimeout, false);
+                EventLogTags.writeDeviceIdle(mState, "no activity");
+            }
+            if (mLightState == LIGHT_STATE_ACTIVE) {
+                mLightState = LIGHT_STATE_INACTIVE;
+                if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
+                resetLightIdleManagementLocked();
+                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
+                EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
+            }
         }
     }
 
@@ -1316,12 +1421,15 @@
         mNextIdlePendingDelay = 0;
         mNextIdleDelay = 0;
         cancelAlarmLocked();
-        cancelSensingAlarmLocked();
         cancelLocatingLocked();
         stopMonitoringMotionLocked();
         mAnyMotionDetector.stop();
     }
 
+    void resetLightIdleManagementLocked() {
+        cancelLightAlarmLocked();
+    }
+
     void exitForceIdleLocked() {
         if (mForceIdle) {
             mForceIdle = false;
@@ -1331,6 +1439,37 @@
         }
     }
 
+    void stepLightIdleStateLocked() {
+        if (mLightState == LIGHT_STATE_OVERRIDE) {
+            // If we are already in full device idle mode, then
+            // there is nothing left to do for light mode.
+            return;
+        }
+
+        if (DEBUG) Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + mLightState);
+        EventLogTags.writeDeviceIdleLightStep();
+
+        switch (mLightState) {
+            case LIGHT_STATE_INACTIVE:
+            case LIGHT_STATE_IDLE_MAINTENANCE:
+                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
+                if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
+                mLightState = LIGHT_STATE_IDLE;
+                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
+                break;
+            case LIGHT_STATE_IDLE:
+                // We have been idling long enough, now it is time to do some work.
+                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT);
+                if (DEBUG) Slog.d(TAG,
+                        "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
+                mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
+                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
+                break;
+        }
+    }
+
     void stepIdleStateLocked() {
         if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
         EventLogTags.writeDeviceIdleStep();
@@ -1361,8 +1500,7 @@
                 mState = STATE_SENSING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
                 EventLogTags.writeDeviceIdle(mState, "step");
-                scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);
-                cancelSensingAlarmLocked();
+                cancelAlarmLocked();
                 cancelLocatingLocked();
                 mAnyMotionDetector.checkForAnyMotion();
                 mNotMoving = false;
@@ -1374,8 +1512,7 @@
                 mState = STATE_LOCATING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
                 EventLogTags.writeDeviceIdle(mState, "step");
-                cancelSensingAlarmLocked();
-                scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);
+                scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
                 if (mLocationManager != null
                         && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
                     mLocationManager.requestLocationUpdates(mLocationRequest,
@@ -1401,7 +1538,7 @@
 
                 // Otherwise, we have to move from locating into idle maintenance.
             case STATE_LOCATING:
-                cancelSensingAlarmLocked();
+                cancelAlarmLocked();
                 cancelLocatingLocked();
                 mAnyMotionDetector.stop();
             case STATE_IDLE_MAINTENANCE:
@@ -1412,6 +1549,10 @@
                 if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
                 mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
                 mState = STATE_IDLE;
+                if (mLightState != LIGHT_STATE_OVERRIDE) {
+                    mLightState = LIGHT_STATE_OVERRIDE;
+                    cancelLightAlarmLocked();
+                }
                 EventLogTags.writeDeviceIdle(mState, "step");
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                 break;
@@ -1439,11 +1580,22 @@
         // The device is not yet active, so we want to go back to the pending idle
         // state to wait again for no motion.  Note that we only monitor for motion
         // after moving out of the inactive state, so no need to worry about that.
+        boolean becomeInactive = false;
         if (mState != STATE_ACTIVE) {
             scheduleReportActiveLocked(type, Process.myUid());
             mState = STATE_ACTIVE;
             mInactiveTimeout = timeout;
             EventLogTags.writeDeviceIdle(mState, type);
+            becomeInactive = true;
+        }
+        if (mLightState == LIGHT_STATE_OVERRIDE) {
+            // We went out of light idle mode because we had started full idle mode...  let's
+            // now go back and reset things so we resume light idling if appropriate.
+            mLightState = STATE_ACTIVE;
+            EventLogTags.writeDeviceIdleLight(mLightState, type);
+            becomeInactive = true;
+        }
+        if (becomeInactive) {
             becomeInactiveIfAppropriateLocked();
         }
     }
@@ -1501,11 +1653,10 @@
         }
     }
 
-    void cancelSensingAlarmLocked() {
-        if (mSensing) {
-            if (DEBUG) Slog.d(TAG, "cancelSensingAlarmLocked()");
-            mAlarmManager.cancel(mSensingAlarmIntent);
-            mSensing = false;
+    void cancelLightAlarmLocked() {
+        if (mNextLightAlarmTime != 0) {
+            mNextLightAlarmTime = 0;
+            mAlarmManager.cancel(mLightAlarmIntent);
         }
     }
 
@@ -1536,13 +1687,18 @@
         }
     }
 
-    void scheduleSensingAlarmLocked(long delay) {
-        if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
-        cancelSensingAlarmLocked();
-        mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
+    void scheduleLightAlarmLocked(long delay) {
+        if (DEBUG) Slog.d(TAG, "scheduleLightAlarmLocked(" + delay + ")");
+        if (mMotionSensor == null) {
+            // If there is no motion sensor on this device, then we won't schedule
+            // alarms, because we can't determine if the device is not moving.  This effectively
+            // turns off normal execution of device idling, although it is still possible to
+            // manually poke it by pretending like the alarm is going off.
+            return;
+        }
+        mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-            mNextAlarmTime, mSensingAlarmIntent);
-        mSensing = true;
+                mNextLightAlarmTime, mLightAlarmIntent);
     }
 
     private static int[] buildAppIdArray(ArrayMap<String, Integer> systemApps,
@@ -1778,7 +1934,21 @@
                 try {
                     exitForceIdleLocked();
                     stepIdleStateLocked();
-                    pw.print("Stepped to: "); pw.println(stateToString(mState));
+                    pw.print("Stepped to: ");
+                    pw.println(stateToString(mState));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        } else if ("light-step".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    exitForceIdleLocked();
+                    stepLightIdleStateLocked();
+                    pw.print("Stepped to: "); pw.println(lightStateToString(mLightState));
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -2041,8 +2211,7 @@
             pw.print("  mScreenOn="); pw.println(mScreenOn);
             pw.print("  mCharging="); pw.println(mCharging);
             pw.print("  mMotionActive="); pw.println(mMotionListener.active);
-            pw.print("  mSensing="); pw.print(mSensing); pw.print(" mNotMoving=");
-                    pw.println(mNotMoving);
+            pw.print("  mNotMoving="); pw.println(mNotMoving);
             pw.print("  mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
                     pw.print(mHasGps); pw.print(" mHasNetwork=");
                     pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated);
@@ -2052,7 +2221,9 @@
             if (mLastGpsLocation != null) {
                 pw.print("  mLastGpsLocation="); pw.println(mLastGpsLocation);
             }
-            pw.print("  mState="); pw.println(stateToString(mState));
+            pw.print("  mState="); pw.print(stateToString(mState));
+            pw.print(" mLightState=");
+            pw.println(lightStateToString(mLightState));
             pw.print("  mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw);
             pw.println();
             if (mNextAlarmTime != 0) {
@@ -2070,6 +2241,11 @@
                 TimeUtils.formatDuration(mNextIdleDelay, pw);
                 pw.println();
             }
+            if (mNextLightAlarmTime != 0) {
+                pw.print("  mNextLightAlarmTime=");
+                TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
+                pw.println();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 9bf2aaa..516e2f4 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -194,6 +194,8 @@
 34006 device_idle_off_start (reason|3)
 34007 device_idle_off_phase (what|3)
 34008 device_idle_off_complete
+34009 device_idle_light (state|1|5), (reason|3)
+34010 device_idle_light_step
 
 # ---------------------------
 # DisplayManagerService.java
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index de48e71..7c0a820 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -25,6 +25,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.os.Environment;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -386,6 +387,12 @@
     }
 
     private int getUserParentOrSelfId(int userId) {
+        // Device supports File Based Encryption, and lock is applied per-user
+        if ("file".equals(SystemProperties.get("ro.crypto.type", "none"))) {
+            return userId;
+        }
+        // Device uses Block Based Encryption, and the parent user's lock is used for the whole
+        // device.
         if (userId != 0) {
             final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
             final UserInfo pi = um.getProfileParent(userId);
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 4d32599..85187c7 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2442,8 +2442,13 @@
         }
 
         try {
-            mCryptConnector.execute("cryptfs", "enablecrypto", "inplace", CRYPTO_TYPES[type],
-                               new SensitiveArg(password));
+            if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
+                mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
+                                CRYPTO_TYPES[type]);
+            } else {
+                mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
+                                CRYPTO_TYPES[type], new SensitiveArg(password));
+            }
         } catch (NativeDaemonConnectorException e) {
             // Encryption failed
             return e.getCode();
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4949138..df6b1d6 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -355,7 +355,8 @@
 
         final ServiceMap smap = getServiceMap(r.userId);
         boolean addToStarting = false;
-        if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) {
+        if (!callerFg && r.app == null
+                && mAm.mUserController.hasStartedUserState(r.userId)) {
             ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
             if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
                 // If this is not coming from a foreground caller, then we may want
@@ -1314,6 +1315,15 @@
         if (!mRestartingServices.contains(r)) {
             return;
         }
+        if (!isServiceNeeded(r, false, false)) {
+            // Paranoia: is this service actually needed?  In theory a service that is not
+            // needed should never remain on the restart list.  In practice...  well, there
+            // have been bugs where this happens, and bad things happen because the process
+            // ends up just being cached, so quickly killed, then restarted again and again.
+            // Let's not let that happen.
+            Slog.wtf(TAG, "Restarting service that is not needed: " + r);
+            return;
+        }
         try {
             bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
         } catch (TransactionTooLargeException e) {
@@ -1392,7 +1402,7 @@
 
         // Make sure that the user who owns this service is started.  If not,
         // we don't want to allow it to run.
-        if (mAm.mStartedUsers.get(r.userId) == null) {
+        if (!mAm.mUserController.hasStartedUserState(r.userId)) {
             String msg = "Unable to launch app "
                     + r.appInfo.packageName + "/"
                     + r.appInfo.uid + " for service "
@@ -2043,6 +2053,13 @@
                             mAm.mProcessStats);
                     realStartServiceLocked(sr, proc, sr.createdFromFg);
                     didSomething = true;
+                    if (!isServiceNeeded(sr, false, false)) {
+                        // We were waiting for this service to start, but it is actually no
+                        // longer needed.  This could happen because bringDownServiceIfNeeded
+                        // won't bring down a service that is pending...  so now the pending
+                        // is done, so let's drop it.
+                        bringDownServiceLocked(sr);
+                    }
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception in new application when starting service "
@@ -2055,7 +2072,7 @@
         // be weird to bring up the process but arbitrarily not let the services
         // run at this point just because their restart time hasn't come up.
         if (mRestartingServices.size() > 0) {
-            ServiceRecord sr = null;
+            ServiceRecord sr;
             for (int i=0; i<mRestartingServices.size(); i++) {
                 sr = mRestartingServices.get(i);
                 if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e7601c2..c728b39 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -79,7 +79,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
-import android.util.SparseIntArray;
 import android.view.Display;
 
 import com.android.internal.R;
@@ -205,7 +204,6 @@
 import android.os.IBinder;
 import android.os.IPermissionController;
 import android.os.IProcessInfoService;
-import android.os.IRemoteCallback;
 import android.os.IUserManager;
 import android.os.Looper;
 import android.os.Message;
@@ -370,17 +368,10 @@
     // How long we wait until we timeout on key dispatching during instrumentation.
     static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000;
 
-    // Amount of time we wait for observers to handle a user switch before
-    // giving up on them and unfreezing the screen.
-    static final int USER_SWITCH_TIMEOUT = 2*1000;
-
     // This is the amount of time an app needs to be running a foreground service before
     // we will consider it to be doing interaction for usage stats.
     static final int SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
 
-    // Maximum number of users we allow to be running at a time.
-    static final int MAX_RUNNING_USERS = 3;
-
     // How long to wait in getAssistContextExtras for the activity and foreground services
     // to respond with the result.
     static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -505,6 +496,8 @@
      */
     String mDeviceOwnerName;
 
+    final UserController mUserController;
+
     public class PendingAssistExtras extends Binder implements Runnable {
         public final ActivityRecord activity;
         public final Bundle extras;
@@ -707,32 +700,6 @@
     final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
 
     /**
-     * Which users have been started, so are allowed to run code.
-     */
-    final SparseArray<UserState> mStartedUsers = new SparseArray<>();
-
-    /**
-     * LRU list of history of current users.  Most recently current is at the end.
-     */
-    final ArrayList<Integer> mUserLru = new ArrayList<Integer>();
-
-    /**
-     * Constant array of the users that are currently started.
-     */
-    int[] mStartedUserArray = new int[] { 0 };
-
-    /**
-     * Registered observers of the user switching mechanics.
-     */
-    final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
-            = new RemoteCallbackList<IUserSwitchObserver>();
-
-    /**
-     * Currently active user switch.
-     */
-    Object mCurUserSwitchCallback;
-
-    /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
      */
@@ -1229,7 +1196,6 @@
     int mSamplingInterval = 0;
     boolean mAutoStopProfiler = false;
     int mProfileType = 0;
-    String mOpenGlTraceApp = null;
     final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>();
     String mMemWatchDumpProcName;
     String mMemWatchDumpFile;
@@ -1301,19 +1267,6 @@
 
     final ActivityThread mSystemThread;
 
-    // Holds the current foreground user's id
-    int mCurrentUserId = 0;
-    // Holds the target user's id during a user switch
-    int mTargetUserId = UserHandle.USER_NULL;
-    // If there are multiple profiles for the current user, their ids are here
-    // Currently only the primary user can have managed profiles
-    int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
-
-    /**
-     * Mapping from each known user ID to the profile group ID it is associated with.
-     */
-    SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
-
     private UserManagerService mUserManager;
 
     private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -1443,7 +1396,7 @@
                     boolean isBackground = (UserHandle.getAppId(proc.uid)
                             >= Process.FIRST_APPLICATION_UID
                             && proc.pid != MY_PID);
-                    for (int userId : mCurrentProfileIds) {
+                    for (int userId : mUserController.mCurrentProfileIds) {
                         isBackground &= (proc.userId != userId);
                     }
                     if (isBackground && !showBackground) {
@@ -1830,15 +1783,15 @@
                 break;
             }
             case REPORT_USER_SWITCH_MSG: {
-                dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                mUserController.dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                 break;
             }
             case CONTINUE_USER_SWITCH_MSG: {
-                continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                mUserController.continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                 break;
             }
             case USER_SWITCH_TIMEOUT_MSG: {
-                timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                mUserController.timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                 break;
             }
             case IMMERSIVE_MODE_LOCK_MSG: {
@@ -1867,7 +1820,7 @@
             }
             case START_PROFILES_MSG: {
                 synchronized (ActivityManagerService.this) {
-                    startProfilesLocked();
+                    mUserController.startProfilesLocked();
                 }
                 break;
             }
@@ -2058,14 +2011,14 @@
                 }
             } break;
             case FOREGROUND_PROFILE_CHANGED_MSG: {
-                dispatchForegroundProfileChanged(msg.arg1);
+                mUserController.dispatchForegroundProfileChanged(msg.arg1);
             } break;
             case REPORT_TIME_TRACKER_MSG: {
                 AppTimeTracker tracker = (AppTimeTracker)msg.obj;
                 tracker.deliverResult(mContext);
             } break;
             case REPORT_USER_SWITCH_COMPLETE_MSG: {
-                dispatchUserSwitchComplete(msg.arg1);
+                mUserController.dispatchUserSwitchComplete(msg.arg1);
             } break;
             case SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG: {
                 IUiAutomationConnection connection = (IUiAutomationConnection) msg.obj;
@@ -2387,10 +2340,7 @@
 
         mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
 
-        // User 0 is the first and only user that runs at boot.
-        mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
-        mUserLru.add(UserHandle.USER_SYSTEM);
-        updateStartedUserArrayLocked();
+        mUserController = new UserController(this);
 
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
             ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -5567,16 +5517,6 @@
                 null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid));
     }
 
-    private void forceStopUserLocked(int userId, String reason) {
-        forceStopPackageLocked(null, -1, false, false, true, false, false, userId, reason);
-        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                | Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        broadcastIntentLocked(null, null, intent,
-                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
-    }
 
     private final boolean killPackageProcessesLocked(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -5740,7 +5680,7 @@
 
     }
 
-    private final boolean forceStopPackageLocked(String packageName, int appId,
+    final boolean forceStopPackageLocked(String packageName, int appId,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
             boolean evenPersistent, boolean uninstalling, int userId, String reason) {
         int i;
@@ -5795,7 +5735,7 @@
         }
 
         boolean didSomething = killPackageProcessesLocked(packageName, appId, userId,
-                -100, callerWillRestart, true, doit, evenPersistent,
+                ProcessList.INVALID_ADJ, callerWillRestart, true, doit, evenPersistent,
                 packageName == null ? ("stop user " + userId) : ("stop " + packageName));
 
         if (mStackSupervisor.finishDisabledPackageActivitiesLocked(
@@ -6111,7 +6051,7 @@
         EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);
 
         app.makeActive(thread, mProcessStats);
-        app.curAdj = app.setAdj = -100;
+        app.curAdj = app.setAdj = ProcessList.INVALID_ADJ;
         app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
         app.forcingToForeground = null;
         updateProcessForegroundLocked(app, false, false);
@@ -6161,11 +6101,6 @@
                 samplingInterval = mSamplingInterval;
                 profileAutoStop = mAutoStopProfiler;
             }
-            boolean enableOpenGlTrace = false;
-            if (mOpenGlTraceApp != null && mOpenGlTraceApp.equals(processName)) {
-                enableOpenGlTrace = true;
-                mOpenGlTraceApp = null;
-            }
             boolean enableTrackAllocation = false;
             if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
                 enableTrackAllocation = true;
@@ -6199,9 +6134,9 @@
             thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                     profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                     app.instrumentationUiAutomationConnection, testMode,
-                    mBinderTransactionTrackingEnabled, enableOpenGlTrace,
-                    enableTrackAllocation, isRestrictedBackupMode || !normalMode,
-                    app.persistent, new Configuration(mConfiguration), app.compat,
+                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
+                    isRestrictedBackupMode || !normalMode, app.persistent,
+                    new Configuration(mConfiguration), app.compat,
                     getCommonServicesLocked(app.isolated),
                     mCoreSettingsObserver.getCoreSettingsLocked());
             updateLruProcessLocked(app, false, null);
@@ -6466,32 +6401,18 @@
                     || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
                     SystemProperties.set("dev.bootcomplete", "1");
                 }
-                for (int i=0; i<mStartedUsers.size(); i++) {
-                    UserState uss = mStartedUsers.valueAt(i);
-                    if (uss.mState == UserState.STATE_BOOTING) {
-                        uss.mState = UserState.STATE_RUNNING;
-                        final int userId = mStartedUsers.keyAt(i);
-                        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-                        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
-                        broadcastIntentLocked(null, null, intent, null,
-                                new IIntentReceiver.Stub() {
-                                    @Override
-                                    public void performReceive(Intent intent, int resultCode,
-                                            String data, Bundle extras, boolean ordered,
-                                            boolean sticky, int sendingUser) {
-                                        synchronized (ActivityManagerService.this) {
-                                            requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-                                                    true, false);
-                                        }
-                                    }
-                                },
-                                0, null, null,
-                                new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-                                AppOpsManager.OP_NONE, null, true, false,
-                                MY_PID, Process.SYSTEM_UID, userId);
-                    }
-                }
+                mUserController.sendBootCompletedLocked(
+                        new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky, int sendingUser) {
+                                synchronized (ActivityManagerService.this) {
+                                    requestPssAllProcsLocked(SystemClock.uptimeMillis(),
+                                            true, false);
+                                }
+                            }
+                        });
                 scheduleStartProfilesLocked();
             }
         }
@@ -9498,7 +9419,7 @@
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
-            int tmpTargetUserId = unsafeConvertIncomingUser(userId);
+            int tmpTargetUserId = unsafeConvertIncomingUserLocked(userId);
             if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
                 if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
                     return null;
@@ -10344,7 +10265,9 @@
         int callingPid = Binder.getCallingPid();
         long ident = 0;
         boolean clearedIdentity = false;
-        userId = unsafeConvertIncomingUser(userId);
+        synchronized (this) {
+            userId = unsafeConvertIncomingUserLocked(userId);
+        }
         if (canClearIdentity(callingPid, callingUid, userId)) {
             clearedIdentity = true;
             ident = Binder.clearCallingIdentity();
@@ -10809,19 +10732,6 @@
         }
     }
 
-    void setOpenGlTraceApp(ApplicationInfo app, String processName) {
-        synchronized (this) {
-            boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
-            if (!isDebuggable) {
-                if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
-                    throw new SecurityException("Process not debuggable: " + app.packageName);
-                }
-            }
-
-            mOpenGlTraceApp = processName;
-        }
-    }
-
     void setTrackAllocationApp(ApplicationInfo app, String processName) {
         synchronized (this) {
             boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
@@ -11024,8 +10934,9 @@
 
     @Override
     public boolean isAssistDataAllowedOnCurrentActivity() {
-        int userId = mCurrentUserId;
+        int userId;
         synchronized (this) {
+            userId = mUserController.mCurrentUserId;
             ActivityRecord activity = getFocusedStack().topActivity();
             if (activity == null) {
                 return false;
@@ -11948,7 +11859,7 @@
 
             // Make sure we have the current profile info, since it is needed for
             // security checks.
-            updateCurrentProfileIdsLocked();
+            mUserController.updateCurrentProfileIdsLocked();
 
             mRecentTasks.clear();
             mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
@@ -12055,18 +11966,20 @@
 
         retrieveSettings();
         loadResourcesOnSystemReady();
-
+        final int currentUserId;
         synchronized (this) {
+            currentUserId = mUserController.mCurrentUserId;
             readGrantedUriPermissionsLocked();
         }
 
         if (goingCallback != null) goingCallback.run();
 
+
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
-                Integer.toString(mCurrentUserId), mCurrentUserId);
+                Integer.toString(currentUserId), currentUserId);
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
-                Integer.toString(mCurrentUserId), mCurrentUserId);
-        mSystemServiceManager.startUser(mCurrentUserId);
+                Integer.toString(currentUserId), currentUserId);
+        mSystemServiceManager.startUser(currentUserId);
 
         synchronized (this) {
             if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -12094,7 +12007,7 @@
 
             // Start up initial activity.
             mBooting = true;
-            startHomeActivityLocked(mCurrentUserId, "systemReady");
+            startHomeActivityLocked(currentUserId, "systemReady");
 
             try {
                 if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
@@ -12115,13 +12028,14 @@
                 Intent intent = new Intent(Intent.ACTION_USER_STARTED);
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
                 broadcastIntentLocked(null, null, intent,
                         null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                        null, false, false, MY_PID, Process.SYSTEM_UID, mCurrentUserId);
+                        null, false, false, MY_PID, Process.SYSTEM_UID,
+                        currentUserId);
                 intent = new Intent(Intent.ACTION_USER_STARTING);
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
                 broadcastIntentLocked(null, null, intent,
                         null, new IIntentReceiver.Stub() {
                             @Override
@@ -12138,7 +12052,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
             mStackSupervisor.resumeTopActivitiesLocked();
-            sendUserSwitchBroadcastsLocked(-1, mCurrentUserId);
+            sendUserSwitchBroadcastsLocked(-1, currentUserId);
         }
     }
 
@@ -12337,7 +12251,7 @@
         // launching the report UI under a different user.
         app.errorReportReceiver = null;
 
-        for (int userId : mCurrentProfileIds) {
+        for (int userId : mUserController.mCurrentProfileIds) {
             if (app.userId == userId) {
                 app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                         mContext, app.info.packageName, app.info.flags);
@@ -13794,38 +13708,7 @@
         if (dumpPackage == null) {
             pw.println();
             needSep = false;
-            pw.println("  mStartedUsers:");
-            for (int i=0; i<mStartedUsers.size(); i++) {
-                UserState uss = mStartedUsers.valueAt(i);
-                pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
-                        pw.print(": "); uss.dump("", pw);
-            }
-            pw.print("  mStartedUserArray: [");
-            for (int i=0; i<mStartedUserArray.length; i++) {
-                if (i > 0) pw.print(", ");
-                pw.print(mStartedUserArray[i]);
-            }
-            pw.println("]");
-            pw.print("  mUserLru: [");
-            for (int i=0; i<mUserLru.size(); i++) {
-                if (i > 0) pw.print(", ");
-                pw.print(mUserLru.get(i));
-            }
-            pw.println("]");
-            if (dumpAll) {
-                pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
-            }
-            synchronized (mUserProfileGroupIdsSelfLocked) {
-                if (mUserProfileGroupIdsSelfLocked.size() > 0) {
-                    pw.println("  mUserProfileGroupIds:");
-                    for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
-                        pw.print("    User #");
-                        pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
-                        pw.print(" -> profile #");
-                        pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
-                    }
-                }
-            }
+            mUserController.dump(pw, dumpAll);
         }
         if (mHomeProcess != null && (dumpPackage == null
                 || mHomeProcess.pkgList.containsKey(dumpPackage))) {
@@ -13936,15 +13819,6 @@
             pw.print("  mMemWatchDumpPid="); pw.print(mMemWatchDumpPid);
                     pw.print(" mMemWatchDumpUid="); pw.println(mMemWatchDumpUid);
         }
-        if (mOpenGlTraceApp != null) {
-            if (dumpPackage == null || dumpPackage.equals(mOpenGlTraceApp)) {
-                if (needSep) {
-                    pw.println();
-                    needSep = false;
-                }
-                pw.println("  mOpenGlTraceApp=" + mOpenGlTraceApp);
-            }
-        }
         if (mTrackAllocationApp != null) {
             if (dumpPackage == null || dumpPackage.equals(mTrackAllocationApp)) {
                 if (needSep) {
@@ -14054,8 +13928,8 @@
         pw.print(": ");
         pw.print(name);
         pw.print(" (");
-        pw.print(mProcessList.getMemLevel(adj)/1024);
-        pw.println(" kB)");
+        pw.print(stringifySize(mProcessList.getMemLevel(adj), 1024));
+        pw.println(")");
     }
 
     boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -14836,7 +14710,7 @@
         for (int i=0; i<items.size(); i++) {
             MemItem mi = items.get(i);
             if (!isCompact) {
-                pw.print(prefix); pw.printf("%7d kB: ", mi.pss); pw.println(mi.label);
+                pw.printf("%s%s: %s\n", prefix, stringifyKBSize(mi.pss), mi.label);
             } else if (mi.isProc) {
                 pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
                 pw.print(","); pw.print(mi.id); pw.print(","); pw.print(mi.pss);
@@ -14912,7 +14786,7 @@
             // short checkin version
             pw.print("time,"); pw.print(uptime); pw.print(","); pw.println(realtime);
         } else {
-            pw.println("Applications Memory Usage (kB):");
+            pw.println("Applications Memory Usage (in Kilobytes):");
             pw.println("Uptime: " + uptime + " Realtime: " + realtime);
         }
     }
@@ -14946,6 +14820,26 @@
         return longOut;
     }
 
+    private static String stringifySize(long size, int order) {
+        Locale locale = Locale.US;
+        switch (order) {
+            case 1:
+                return String.format(locale, "%,13d", size);
+            case 1024:
+                return String.format(locale, "%,9dK", size / 1024);
+            case 1024 * 1024:
+                return String.format(locale, "%,5dM", size / 1024 / 1024);
+            case 1024 * 1024 * 1024:
+                return String.format(locale, "%,1dG", size / 1024 / 1024 / 1024);
+            default:
+                throw new IllegalArgumentException("Invalid size order");
+        }
+    }
+
+    private static String stringifyKBSize(long size) {
+        return stringifySize(size * 1024, 1024);
+    }
+
     final void dumpApplicationMemoryUsage(FileDescriptor fd,
             PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
         boolean dumpDetails = false;
@@ -15299,8 +15193,8 @@
             }
             if (!brief) {
                 if (!isCompact) {
-                    pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb());
-                    pw.print(" kB (status ");
+                    pw.print("Total RAM: "); pw.print(stringifyKBSize(memInfo.getTotalSizeKb()));
+                    pw.print(" (status ");
                     switch (mLastMemoryLevel) {
                         case ProcessStats.ADJ_MEM_FACTOR_NORMAL:
                             pw.println("normal)");
@@ -15319,11 +15213,16 @@
                             pw.println(")");
                             break;
                     }
-                    pw.print(" Free RAM: "); pw.print(cachedPss + memInfo.getCachedSizeKb()
-                            + memInfo.getFreeSizeKb()); pw.print(" kB (");
-                            pw.print(cachedPss); pw.print(" cached pss + ");
-                            pw.print(memInfo.getCachedSizeKb()); pw.print(" cached kernel + ");
-                            pw.print(memInfo.getFreeSizeKb()); pw.println(" free)");
+                    pw.print(" Free RAM: ");
+                    pw.print(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb()
+                            + memInfo.getFreeSizeKb()));
+                    pw.print(" (");
+                    pw.print(stringifyKBSize(cachedPss));
+                    pw.print(" cached pss + ");
+                    pw.print(stringifyKBSize(memInfo.getCachedSizeKb()));
+                    pw.print(" cached kernel + ");
+                    pw.print(stringifyKBSize(memInfo.getFreeSizeKb()));
+                    pw.println(" free)");
                 } else {
                     pw.print("ram,"); pw.print(memInfo.getTotalSizeKb()); pw.print(",");
                     pw.print(cachedPss + memInfo.getCachedSizeKb()
@@ -15332,24 +15231,25 @@
                 }
             }
             if (!isCompact) {
-                pw.print(" Used RAM: "); pw.print(totalPss - cachedPss
-                        + memInfo.getKernelUsedSizeKb()); pw.print(" kB (");
-                        pw.print(totalPss - cachedPss); pw.print(" used pss + ");
-                        pw.print(memInfo.getKernelUsedSizeKb()); pw.print(" kernel)\n");
-                pw.print(" Lost RAM: "); pw.print(memInfo.getTotalSizeKb()
+                pw.print(" Used RAM: "); pw.print(stringifyKBSize(totalPss - cachedPss
+                        + memInfo.getKernelUsedSizeKb())); pw.print(" (");
+                pw.print(stringifyKBSize(totalPss - cachedPss)); pw.print(" used pss + ");
+                pw.print(stringifyKBSize(memInfo.getKernelUsedSizeKb())); pw.print(" kernel)\n");
+                pw.print(" Lost RAM: "); pw.println(stringifyKBSize(memInfo.getTotalSizeKb()
                         - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
-                        - memInfo.getKernelUsedSizeKb()); pw.println(" kB");
+                        - memInfo.getKernelUsedSizeKb()));
             }
             if (!brief) {
                 if (memInfo.getZramTotalSizeKb() != 0) {
                     if (!isCompact) {
-                        pw.print("     ZRAM: "); pw.print(memInfo.getZramTotalSizeKb());
-                                pw.print(" kB physical used for ");
-                                pw.print(memInfo.getSwapTotalSizeKb()
-                                        - memInfo.getSwapFreeSizeKb());
-                                pw.print(" kB in swap (");
-                                pw.print(memInfo.getSwapTotalSizeKb());
-                                pw.println(" kB total swap)");
+                        pw.print("     ZRAM: ");
+                        pw.print(stringifyKBSize(memInfo.getZramTotalSizeKb()));
+                                pw.print(" physical used for ");
+                                pw.print(stringifyKBSize(memInfo.getSwapTotalSizeKb()
+                                        - memInfo.getSwapFreeSizeKb()));
+                                pw.print(" in swap (");
+                                pw.print(stringifyKBSize(memInfo.getSwapTotalSizeKb()));
+                                pw.println(" total swap)");
                     } else {
                         pw.print("zram,"); pw.print(memInfo.getZramTotalSizeKb()); pw.print(",");
                                 pw.print(memInfo.getSwapTotalSizeKb()); pw.print(",");
@@ -15360,23 +15260,23 @@
                 if (!isCompact) {
                     if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
                             || ksm[KSM_VOLATILE] != 0) {
-                        pw.print("      KSM: "); pw.print(ksm[KSM_SHARING]);
-                                pw.print(" kB saved from shared ");
-                                pw.print(ksm[KSM_SHARED]); pw.println(" kB");
-                        pw.print("           "); pw.print(ksm[KSM_UNSHARED]);
-                                pw.print(" kB unshared; ");
-                                pw.print(ksm[KSM_VOLATILE]); pw.println(" kB volatile");
+                        pw.print("      KSM: "); pw.print(stringifyKBSize(ksm[KSM_SHARING]));
+                                pw.print(" saved from shared ");
+                                pw.print(stringifyKBSize(ksm[KSM_SHARED]));
+                        pw.print("           "); pw.print(stringifyKBSize(ksm[KSM_UNSHARED]));
+                                pw.print(" unshared; ");
+                                pw.print(stringifyKBSize(
+                                             ksm[KSM_VOLATILE])); pw.println(" volatile");
                     }
                     pw.print("   Tuning: ");
                     pw.print(ActivityManager.staticGetMemoryClass());
                     pw.print(" (large ");
                     pw.print(ActivityManager.staticGetLargeMemoryClass());
                     pw.print("), oom ");
-                    pw.print(mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024);
-                    pw.print(" kB");
+                    pw.print(stringifySize(
+                                mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ), 1024));
                     pw.print(", restore limit ");
-                    pw.print(mProcessList.getCachedRestoreThresholdKb());
-                    pw.print(" kB");
+                    pw.print(stringifyKBSize(mProcessList.getCachedRestoreThresholdKb()));
                     if (ActivityManager.isLowRamDeviceStatic()) {
                         pw.print(" (low-ram)");
                     }
@@ -15414,12 +15314,12 @@
         sb.append(ProcessList.makeProcStateString(procState));
         sb.append(' ');
         ProcessList.appendRamKb(sb, pss);
-        sb.append(" kB: ");
+        sb.append(": ");
         sb.append(name);
         if (memtrack > 0) {
             sb.append(" (");
-            sb.append(memtrack);
-            sb.append(" kB memtrack)");
+            sb.append(stringifyKBSize(memtrack));
+            sb.append(" memtrack)");
         }
     }
 
@@ -15573,11 +15473,11 @@
 
         fullJavaBuilder.append("           ");
         ProcessList.appendRamKb(fullJavaBuilder, totalPss);
-        fullJavaBuilder.append(" kB: TOTAL");
+        fullJavaBuilder.append(": TOTAL");
         if (totalMemtrack > 0) {
             fullJavaBuilder.append(" (");
-            fullJavaBuilder.append(totalMemtrack);
-            fullJavaBuilder.append(" kB memtrack)");
+            fullJavaBuilder.append(stringifyKBSize(totalMemtrack));
+            fullJavaBuilder.append(" memtrack)");
         } else {
         }
         fullJavaBuilder.append("\n");
@@ -15589,47 +15489,54 @@
         StringBuilder memInfoBuilder = new StringBuilder(1024);
         Debug.getMemInfo(infos);
         memInfoBuilder.append("  MemInfo: ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_SLAB]).append(" kB slab, ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_SHMEM]).append(" kB shmem, ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_VM_ALLOC_USED]).append(" kB vm alloc, ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_PAGE_TABLES]).append(" kB page tables ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_KERNEL_STACK]).append(" kB kernel stack\n");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SLAB])).append(" slab, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SHMEM])).append(" shmem, ");
+        memInfoBuilder.append(stringifyKBSize(
+                                  infos[Debug.MEMINFO_VM_ALLOC_USED])).append(" vm alloc, ");
+        memInfoBuilder.append(stringifyKBSize(
+                                  infos[Debug.MEMINFO_PAGE_TABLES])).append(" page tables ");
+        memInfoBuilder.append(stringifyKBSize(
+                                  infos[Debug.MEMINFO_KERNEL_STACK])).append(" kernel stack\n");
         memInfoBuilder.append("           ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_BUFFERS]).append(" kB buffers, ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_CACHED]).append(" kB cached, ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_MAPPED]).append(" kB mapped, ");
-        memInfoBuilder.append(infos[Debug.MEMINFO_FREE]).append(" kB free\n");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_BUFFERS])).append(" buffers, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_CACHED])).append(" cached, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_MAPPED])).append(" mapped, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_FREE])).append(" free\n");
         if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) {
             memInfoBuilder.append("  ZRAM: ");
-            memInfoBuilder.append(infos[Debug.MEMINFO_ZRAM_TOTAL]);
-            memInfoBuilder.append(" kB RAM, ");
-            memInfoBuilder.append(infos[Debug.MEMINFO_SWAP_TOTAL]);
-            memInfoBuilder.append(" kB swap total, ");
-            memInfoBuilder.append(infos[Debug.MEMINFO_SWAP_FREE]);
-            memInfoBuilder.append(" kB swap free\n");
+            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_ZRAM_TOTAL]));
+            memInfoBuilder.append(" RAM, ");
+            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_TOTAL]));
+            memInfoBuilder.append(" swap total, ");
+            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_FREE]));
+            memInfoBuilder.append(" swap free\n");
         }
         final long[] ksm = getKsmInfo();
         if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
                 || ksm[KSM_VOLATILE] != 0) {
-            memInfoBuilder.append("  KSM: "); memInfoBuilder.append(ksm[KSM_SHARING]);
-            memInfoBuilder.append(" kB saved from shared ");
-            memInfoBuilder.append(ksm[KSM_SHARED]); memInfoBuilder.append(" kB\n");
-            memInfoBuilder.append("       "); memInfoBuilder.append(ksm[KSM_UNSHARED]);
-            memInfoBuilder.append(" kB unshared; ");
-            memInfoBuilder.append(ksm[KSM_VOLATILE]); memInfoBuilder.append(" kB volatile\n");
+            memInfoBuilder.append("  KSM: ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARING]));
+            memInfoBuilder.append(" saved from shared ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARED]));
+            memInfoBuilder.append("\n       ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_UNSHARED]));
+            memInfoBuilder.append(" unshared; ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_VOLATILE]));
+            memInfoBuilder.append(" volatile\n");
         }
         memInfoBuilder.append("  Free RAM: ");
-        memInfoBuilder.append(cachedPss + memInfo.getCachedSizeKb()
-                + memInfo.getFreeSizeKb());
-        memInfoBuilder.append(" kB\n");
+        memInfoBuilder.append(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb()
+                + memInfo.getFreeSizeKb()));
+        memInfoBuilder.append("\n");
         memInfoBuilder.append("  Used RAM: ");
-        memInfoBuilder.append(totalPss - cachedPss + memInfo.getKernelUsedSizeKb());
-        memInfoBuilder.append(" kB\n");
+        memInfoBuilder.append(stringifyKBSize(
+                                  totalPss - cachedPss + memInfo.getKernelUsedSizeKb()));
+        memInfoBuilder.append("\n");
         memInfoBuilder.append("  Lost RAM: ");
-        memInfoBuilder.append(memInfo.getTotalSizeKb()
+        memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
                 - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
-                - memInfo.getKernelUsedSizeKb());
-        memInfoBuilder.append(" kB\n");
+                - memInfo.getKernelUsedSizeKb()));
+        memInfoBuilder.append("\n");
         Slog.i(TAG, "Low on memory:");
         Slog.i(TAG, shortNativeBuilder.toString());
         Slog.i(TAG, fullJavaBuilder.toString());
@@ -16093,9 +16000,9 @@
                 requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
     }
 
-    int unsafeConvertIncomingUser(int userId) {
+    int unsafeConvertIncomingUserLocked(int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
-                ? mCurrentUserId : userId;
+                ? mUserController.mCurrentUserId : userId;
     }
 
     int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
@@ -16111,7 +16018,7 @@
         // the value the caller will receive and someone else changing it.
         // We assume that USER_CURRENT_OR_SELF will use the current user; later
         // we will switch to the calling user if access to the current user fails.
-        int targetUserId = unsafeConvertIncomingUser(userId);
+        int targetUserId = unsafeConvertIncomingUserLocked(userId);
 
         if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
             final boolean allow;
@@ -16132,14 +16039,7 @@
             } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
                 // We may or may not allow this depending on whether the two users are
                 // in the same profile.
-                synchronized (mUserProfileGroupIdsSelfLocked) {
-                    int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
-                            UserInfo.NO_PROFILE_GROUP_ID);
-                    int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
-                            UserInfo.NO_PROFILE_GROUP_ID);
-                    allow = callingProfile != UserInfo.NO_PROFILE_GROUP_ID
-                            && callingProfile == targetProfile;
-                }
+                allow = mUserController.isSameProfileGroup(callingUserId, targetUserId);
             } else {
                 throw new IllegalArgumentException("Unknown mode: " + allowMode);
             }
@@ -16758,7 +16658,7 @@
         return receivers;
     }
 
-    private final int broadcastIntentLocked(ProcessRecord callerApp,
+    final int broadcastIntentLocked(ProcessRecord callerApp,
             String callerPackage, Intent intent, String resolvedType,
             IIntentReceiver resultTo, int resultCode, String resultData,
             Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle options,
@@ -17075,7 +16975,7 @@
         int[] users;
         if (userId == UserHandle.USER_ALL) {
             // Caller wants broadcast to go to all started users.
-            users = mStartedUserArray;
+            users = mUserController.getStartedUserArrayLocked();
         } else {
             // Caller wants broadcast to go to one specific user.
             users = new int[] {userId};
@@ -17662,6 +17562,12 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
+    void updateUserConfigurationLocked() {
+        Configuration configuration = new Configuration(mConfiguration);
+        Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
+                mUserController.mCurrentUserId);
+        updateConfigurationLocked(configuration, null, false);
+    }
 
     boolean updateConfigurationLocked(Configuration values,
             ActivityRecord starting, boolean initLocale) {
@@ -17707,7 +17613,8 @@
                 newConfig.seq = mConfigurationSeq;
                 mConfiguration = newConfig;
                 Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
-                mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
+                mUsageStatsService.reportConfigurationChange(newConfig,
+                        mUserController.mCurrentUserId);
                 //mUsageStatsService.noteStartConfig(newConfig);
 
                 final Configuration configCopy = new Configuration(mConfiguration);
@@ -18052,6 +17959,7 @@
 
         // Examine all activities if not already foreground.
         if (!foregroundActivities && activitiesSize > 0) {
+            int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX;
             for (int j = 0; j < activitiesSize; j++) {
                 final ActivityRecord r = app.activities.get(j);
                 if (r.app != app) {
@@ -18072,6 +17980,12 @@
                     app.cached = false;
                     app.empty = false;
                     foregroundActivities = true;
+                    if (r.task != null && minLayer > 0) {
+                        final int layer = r.task.mLayerRank;
+                        if (layer >= 0 && minLayer > layer) {
+                            minLayer = layer;
+                        }
+                    }
                     break;
                 } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) {
                     if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
@@ -18112,6 +18026,9 @@
                     }
                 }
             }
+            if (adj == ProcessList.VISIBLE_APP_ADJ) {
+                adj += minLayer;
+            }
         }
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
@@ -18321,11 +18238,11 @@
                                         && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
                                         && adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                                     adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                                } else if (clientAdj > ProcessList.VISIBLE_APP_ADJ) {
+                                } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
                                     adj = clientAdj;
                                 } else {
                                     if (adj > ProcessList.VISIBLE_APP_ADJ) {
-                                        adj = ProcessList.VISIBLE_APP_ADJ;
+                                        adj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
                                     }
                                 }
                                 if (!client.cached) {
@@ -19181,7 +19098,7 @@
             String authority) {
         if (app == null) return;
         if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-            UserState userState = mStartedUsers.get(app.userId);
+            UserState userState = mUserController.getStartedUserState(app.userId);
             if (userState == null) return;
             final long now = SystemClock.elapsedRealtime();
             Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -19363,6 +19280,8 @@
             uidRec.reset();
         }
 
+        mStackSupervisor.rankTaskLayersIfNeeded();
+
         mAdjSeq++;
         mNewNumServiceProcs = 0;
         mNewNumAServiceProcs = 0;
@@ -20059,44 +19978,18 @@
      */
     @Override
     public boolean startUserInBackground(final int userId) {
-        return startUser(userId, /* foreground */ false);
+        return mUserController.startUser(userId, /* foreground */ false);
     }
 
     /**
      * Start user, if its not already running, and bring it to foreground.
      */
     boolean startUserInForeground(final int userId, Dialog dlg) {
-        boolean result = startUser(userId, /* foreground */ true);
+        boolean result = mUserController.startUser(userId, /* foreground */ true);
         dlg.dismiss();
         return result;
     }
 
-    /**
-     * Refreshes the list of users related to the current user when either a
-     * user switch happens or when a new related user is started in the
-     * background.
-     */
-    private void updateCurrentProfileIdsLocked() {
-        final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
-                mCurrentUserId, false /* enabledOnly */);
-        int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
-        for (int i = 0; i < currentProfileIds.length; i++) {
-            currentProfileIds[i] = profiles.get(i).id;
-        }
-        mCurrentProfileIds = currentProfileIds;
-
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            mUserProfileGroupIdsSelfLocked.clear();
-            final List<UserInfo> users = getUserManagerLocked().getUsers(false);
-            for (int i = 0; i < users.size(); i++) {
-                UserInfo user = users.get(i);
-                if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
-                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
-                }
-            }
-        }
-    }
-
     private Set<Integer> getProfileIdsLocked(int userId) {
         Set<Integer> userIds = new HashSet<Integer>();
         final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
@@ -20122,7 +20015,7 @@
                 return false;
             }
             userName = userInfo.name;
-            mTargetUserId = userId;
+            mUserController.mTargetUserId = userId;
         }
         mUiHandler.removeMessages(START_USER_SWITCH_MSG);
         mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
@@ -20136,186 +20029,6 @@
         d.show();
     }
 
-    private boolean startUser(final int userId, final boolean foreground) {
-        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: switchUser() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS_FULL;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-
-        if (DEBUG_MU) Slog.i(TAG_MU, "starting userid:" + userId + " fore:" + foreground);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (this) {
-                final int oldUserId = mCurrentUserId;
-                if (oldUserId == userId) {
-                    return true;
-                }
-
-                mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
-                        "startUser", false);
-
-                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
-                if (userInfo == null) {
-                    Slog.w(TAG, "No user info for user #" + userId);
-                    return false;
-                }
-                if (foreground && userInfo.isManagedProfile()) {
-                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
-                    return false;
-                }
-
-                if (foreground) {
-                    mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
-                            R.anim.screen_user_enter);
-                }
-
-                boolean needStart = false;
-
-                // If the user we are switching to is not currently started, then
-                // we need to start it now.
-                if (mStartedUsers.get(userId) == null) {
-                    mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                }
-
-                final Integer userIdInt = Integer.valueOf(userId);
-                mUserLru.remove(userIdInt);
-                mUserLru.add(userIdInt);
-
-                if (foreground) {
-                    mCurrentUserId = userId;
-                    updateUserConfigurationLocked();
-                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
-                    updateCurrentProfileIdsLocked();
-                    mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
-                    // Once the internal notion of the active user has switched, we lock the device
-                    // with the option to show the user switcher on the keyguard.
-                    mWindowManager.lockNow(null);
-                } else {
-                    final Integer currentUserIdInt = Integer.valueOf(mCurrentUserId);
-                    updateCurrentProfileIdsLocked();
-                    mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
-                    mUserLru.remove(currentUserIdInt);
-                    mUserLru.add(currentUserIdInt);
-                }
-
-                final UserState uss = mStartedUsers.get(userId);
-
-                // Make sure user is in the started state.  If it is currently
-                // stopping, we need to knock that off.
-                if (uss.mState == UserState.STATE_STOPPING) {
-                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
-                    // so we can just fairly silently bring the user back from
-                    // the almost-dead.
-                    uss.mState = UserState.STATE_RUNNING;
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                } else if (uss.mState == UserState.STATE_SHUTDOWN) {
-                    // This means ACTION_SHUTDOWN has been sent, so we will
-                    // need to treat this as a new boot of the user.
-                    uss.mState = UserState.STATE_BOOTING;
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                }
-
-                if (uss.mState == UserState.STATE_BOOTING) {
-                    // Booting up a new user, need to tell system services about it.
-                    // Note that this is on the same handler as scheduling of broadcasts,
-                    // which is important because it needs to go first.
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
-                }
-
-                if (foreground) {
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
-                            oldUserId));
-                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
-                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
-                            oldUserId, userId, uss));
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
-                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
-                }
-
-                if (needStart) {
-                    // Send USER_STARTED broadcast
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    broadcastIntentLocked(null, null, intent,
-                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, Process.SYSTEM_UID, userId);
-                }
-
-                if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
-                    if (userId != UserHandle.USER_SYSTEM) {
-                        Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
-                        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                        broadcastIntentLocked(null, null, intent, null,
-                                new IIntentReceiver.Stub() {
-                                    public void performReceive(Intent intent, int resultCode,
-                                            String data, Bundle extras, boolean ordered,
-                                            boolean sticky, int sendingUser) {
-                                        onUserInitialized(uss, foreground, oldUserId, userId);
-                                    }
-                                }, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, true, false, MY_PID, Process.SYSTEM_UID, userId);
-                        uss.initializing = true;
-                    } else {
-                        getUserManagerLocked().makeInitialized(userInfo.id);
-                    }
-                }
-
-                if (foreground) {
-                    if (!uss.initializing) {
-                        moveUserToForegroundLocked(uss, oldUserId, userId);
-                    }
-                } else {
-                    mStackSupervisor.startBackgroundUserLocked(userId, uss);
-                }
-
-                if (needStart) {
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    broadcastIntentLocked(null, null, intent,
-                            null, new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered, boolean sticky,
-                                        int sendingUser) throws RemoteException {
-                                }
-                            }, 0, null, null,
-                            new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        return true;
-    }
-
-    void dispatchForegroundProfileChanged(int userId) {
-        final int N = mUserSwitchObservers.beginBroadcast();
-        for (int i = 0; i < N; i++) {
-            try {
-                mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
-            } catch (RemoteException e) {
-                // Ignore
-            }
-        }
-        mUserSwitchObservers.finishBroadcast();
-    }
-
     void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
         long ident = Binder.clearCallingIdentity();
         try {
@@ -20364,150 +20077,6 @@
         }
     }
 
-    void dispatchUserSwitch(final UserState uss, final int oldUserId,
-            final int newUserId) {
-        final int N = mUserSwitchObservers.beginBroadcast();
-        if (N > 0) {
-            final IRemoteCallback callback = new IRemoteCallback.Stub() {
-                int mCount = 0;
-                @Override
-                public void sendResult(Bundle data) throws RemoteException {
-                    synchronized (ActivityManagerService.this) {
-                        if (mCurUserSwitchCallback == this) {
-                            mCount++;
-                            if (mCount == N) {
-                                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
-                            }
-                        }
-                    }
-                }
-            };
-            synchronized (this) {
-                uss.switching = true;
-                mCurUserSwitchCallback = callback;
-            }
-            for (int i=0; i<N; i++) {
-                try {
-                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
-                            newUserId, callback);
-                } catch (RemoteException e) {
-                }
-            }
-        } else {
-            synchronized (this) {
-                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
-            }
-        }
-        mUserSwitchObservers.finishBroadcast();
-    }
-
-    void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
-        synchronized (this) {
-            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
-            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
-        }
-    }
-
-    void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
-        mCurUserSwitchCallback = null;
-        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
-                oldUserId, newUserId, uss));
-    }
-
-    void onUserInitialized(UserState uss, boolean foreground, int oldUserId, int newUserId) {
-        synchronized (this) {
-            if (foreground) {
-                moveUserToForegroundLocked(uss, oldUserId, newUserId);
-            }
-        }
-
-        completeSwitchAndInitialize(uss, newUserId, true, false);
-    }
-
-    void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
-        boolean homeInFront = mStackSupervisor.switchUserLocked(newUserId, uss);
-        if (homeInFront) {
-            startHomeActivityLocked(newUserId, "moveUserToFroreground");
-        } else {
-            mStackSupervisor.resumeTopActivitiesLocked();
-        }
-        EventLogTags.writeAmSwitchUser(newUserId);
-        getUserManagerLocked().onUserForeground(newUserId);
-        sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
-    }
-
-    private void updateUserConfigurationLocked() {
-        Configuration configuration = new Configuration(mConfiguration);
-        Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
-                mCurrentUserId);
-        updateConfigurationLocked(configuration, null, false);
-    }
-
-    void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
-        completeSwitchAndInitialize(uss, newUserId, false, true);
-    }
-
-    void completeSwitchAndInitialize(UserState uss, int newUserId,
-            boolean clearInitializing, boolean clearSwitching) {
-        boolean unfrozen = false;
-        synchronized (this) {
-            if (clearInitializing) {
-                uss.initializing = false;
-                getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
-            }
-            if (clearSwitching) {
-                uss.switching = false;
-            }
-            if (!uss.switching && !uss.initializing) {
-                mWindowManager.stopFreezingScreen();
-                unfrozen = true;
-            }
-        }
-        if (unfrozen) {
-            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
-            mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
-                    newUserId, 0));
-        }
-        stopGuestUserIfBackground();
-    }
-
-    /** Called on handler thread */
-    void dispatchUserSwitchComplete(int userId) {
-        final int observerCount = mUserSwitchObservers.beginBroadcast();
-        for (int i = 0; i < observerCount; i++) {
-            try {
-                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
-            } catch (RemoteException e) {
-            }
-        }
-        mUserSwitchObservers.finishBroadcast();
-    }
-
-    /**
-     * Stops the guest user if it has gone to the background.
-     */
-    private void stopGuestUserIfBackground() {
-        synchronized (this) {
-            final int num = mUserLru.size();
-            for (int i = 0; i < num; i++) {
-                Integer oldUserId = mUserLru.get(i);
-                UserState oldUss = mStartedUsers.get(oldUserId);
-                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
-                        || oldUss.mState == UserState.STATE_STOPPING
-                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
-                    continue;
-                }
-                UserInfo userInfo = mUserManager.getUserInfo(oldUserId);
-                if (userInfo.isGuest()) {
-                    // This is a user to be stopped.
-                    stopUserLocked(oldUserId, null);
-                    break;
-                }
-            }
-        }
-    }
-
     void scheduleStartProfilesLocked() {
         if (!mHandler.hasMessages(START_PROFILES_MSG)) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
@@ -20515,229 +20084,9 @@
         }
     }
 
-    void startProfilesLocked() {
-        if (DEBUG_MU) Slog.i(TAG_MU, "startProfilesLocked");
-        List<UserInfo> profiles = getUserManagerLocked().getProfiles(
-                mCurrentUserId, false /* enabledOnly */);
-        List<UserInfo> toStart = new ArrayList<UserInfo>(profiles.size());
-        for (UserInfo user : profiles) {
-            if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
-                    && user.id != mCurrentUserId) {
-                toStart.add(user);
-            }
-        }
-        final int n = toStart.size();
-        int i = 0;
-        for (; i < n && i < (MAX_RUNNING_USERS - 1); ++i) {
-            startUserInBackground(toStart.get(i).id);
-        }
-        if (i < n) {
-            Slog.w(TAG_MU, "More profiles than MAX_RUNNING_USERS");
-        }
-    }
-
-    void finishUserBoot(UserState uss) {
-        synchronized (this) {
-            if (uss.mState == UserState.STATE_BOOTING
-                    && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
-                uss.mState = UserState.STATE_RUNNING;
-                final int userId = uss.mHandle.getIdentifier();
-                Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
-                broadcastIntentLocked(null, null, intent,
-                        null, null, 0, null, null,
-                        new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-                        AppOpsManager.OP_NONE, null, true, false, MY_PID, Process.SYSTEM_UID,
-                        userId);
-            }
-        }
-    }
-
-    void finishUserSwitch(UserState uss) {
-        synchronized (this) {
-            finishUserBoot(uss);
-
-            startProfilesLocked();
-
-            int num = mUserLru.size();
-            int i = 0;
-            while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
-                Integer oldUserId = mUserLru.get(i);
-                UserState oldUss = mStartedUsers.get(oldUserId);
-                if (oldUss == null) {
-                    // Shouldn't happen, but be sane if it does.
-                    mUserLru.remove(i);
-                    num--;
-                    continue;
-                }
-                if (oldUss.mState == UserState.STATE_STOPPING
-                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
-                    // This user is already stopping, doesn't count.
-                    num--;
-                    i++;
-                    continue;
-                }
-                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
-                    // Owner/System user and current user can't be stopped. We count it as running
-                    // when it is not a pure system user.
-                    if (UserInfo.isSystemOnly(oldUserId)) {
-                        num--;
-                    }
-                    i++;
-                    continue;
-                }
-                // This is a user to be stopped.
-                stopUserLocked(oldUserId, null);
-                num--;
-                i++;
-            }
-        }
-    }
-
     @Override
     public int stopUser(final int userId, final IStopUserCallback callback) {
-        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: switchUser() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS_FULL;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
-            throw new IllegalArgumentException("Can't stop system user " + userId);
-        }
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
-        synchronized (this) {
-            return stopUserLocked(userId, callback);
-        }
-    }
-
-    private int stopUserLocked(final int userId, final IStopUserCallback callback) {
-        if (DEBUG_MU) Slog.i(TAG_MU, "stopUserLocked userId=" + userId);
-        if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
-            return ActivityManager.USER_OP_IS_CURRENT;
-        }
-
-        final UserState uss = mStartedUsers.get(userId);
-        if (uss == null) {
-            // User is not started, nothing to do...  but we do need to
-            // callback if requested.
-            if (callback != null) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            callback.userStopped(userId);
-                        } catch (RemoteException e) {
-                        }
-                    }
-                });
-            }
-            return ActivityManager.USER_OP_SUCCESS;
-        }
-
-        if (callback != null) {
-            uss.mStopCallbacks.add(callback);
-        }
-
-        if (uss.mState != UserState.STATE_STOPPING
-                && uss.mState != UserState.STATE_SHUTDOWN) {
-            uss.mState = UserState.STATE_STOPPING;
-            updateStartedUserArrayLocked();
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                // We are going to broadcast ACTION_USER_STOPPING and then
-                // once that is done send a final ACTION_SHUTDOWN and then
-                // stop the user.
-                final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
-                stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
-                final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
-                // This is the result receiver for the final shutdown broadcast.
-                final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
-                    @Override
-                    public void performReceive(Intent intent, int resultCode, String data,
-                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        finishUserStop(uss);
-                    }
-                };
-                // This is the result receiver for the initial stopping broadcast.
-                final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
-                    @Override
-                    public void performReceive(Intent intent, int resultCode, String data,
-                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        // On to the next.
-                        synchronized (ActivityManagerService.this) {
-                            if (uss.mState != UserState.STATE_STOPPING) {
-                                // Whoops, we are being started back up.  Abort, abort!
-                                return;
-                            }
-                            uss.mState = UserState.STATE_SHUTDOWN;
-                        }
-                        mBatteryStatsService.noteEvent(
-                                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
-                                Integer.toString(userId), userId);
-                        mSystemServiceManager.stopUser(userId);
-                        broadcastIntentLocked(null, null, shutdownIntent,
-                                null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, true, false, MY_PID, Process.SYSTEM_UID, userId);
-                    }
-                };
-                // Kick things off.
-                broadcastIntentLocked(null, null, stoppingIntent,
-                        null, stoppingReceiver, 0, null, null,
-                        new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                        null, true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        return ActivityManager.USER_OP_SUCCESS;
-    }
-
-    void finishUserStop(UserState uss) {
-        final int userId = uss.mHandle.getIdentifier();
-        boolean stopped;
-        ArrayList<IStopUserCallback> callbacks;
-        synchronized (this) {
-            callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks);
-            if (mStartedUsers.get(userId) != uss) {
-                stopped = false;
-            } else if (uss.mState != UserState.STATE_SHUTDOWN) {
-                stopped = false;
-            } else {
-                stopped = true;
-                // User can no longer run.
-                mStartedUsers.remove(userId);
-                mUserLru.remove(Integer.valueOf(userId));
-                updateStartedUserArrayLocked();
-
-                // Clean up all state and processes associated with the user.
-                // Kill all the processes for the user.
-                forceStopUserLocked(userId, "finish user");
-            }
-        }
-
-        for (int i=0; i<callbacks.size(); i++) {
-            try {
-                if (stopped) callbacks.get(i).userStopped(userId);
-                else callbacks.get(i).userStopAborted(userId);
-            } catch (RemoteException e) {
-            }
-        }
-
-        if (stopped) {
-            mSystemServiceManager.cleanupUser(userId);
-            synchronized (this) {
-                mStackSupervisor.removeUserLocked(userId);
-            }
-        }
+        return mUserController.stopUser(userId, callback);
     }
 
     void onUserRemovedLocked(int userId) {
@@ -20746,25 +20095,7 @@
 
     @Override
     public UserInfo getCurrentUser() {
-        if ((checkCallingPermission(INTERACT_ACROSS_USERS)
-                != PackageManager.PERMISSION_GRANTED) && (
-                checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED)) {
-            String msg = "Permission Denial: getCurrentUser() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        synchronized (this) {
-            int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
-            return getUserManagerLocked().getUserInfo(userId);
-        }
-    }
-
-    int getCurrentUserIdLocked() {
-        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        return mUserController.getCurrentUser();
     }
 
     @Override
@@ -20784,7 +20115,7 @@
     }
 
     boolean isUserRunningLocked(int userId, boolean orStopped) {
-        UserState state = mStartedUsers.get(userId);
+        UserState state = mUserController.getStartedUserState(userId);
         if (state == null) {
             return false;
         }
@@ -20807,50 +20138,18 @@
             throw new SecurityException(msg);
         }
         synchronized (this) {
-            return mStartedUserArray;
-        }
-    }
-
-    private void updateStartedUserArrayLocked() {
-        int num = 0;
-        for (int i=0; i<mStartedUsers.size();  i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            // This list does not include stopping users.
-            if (uss.mState != UserState.STATE_STOPPING
-                    && uss.mState != UserState.STATE_SHUTDOWN) {
-                num++;
-            }
-        }
-        mStartedUserArray = new int[num];
-        num = 0;
-        for (int i=0; i<mStartedUsers.size();  i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            if (uss.mState != UserState.STATE_STOPPING
-                    && uss.mState != UserState.STATE_SHUTDOWN) {
-                mStartedUserArray[num] = mStartedUsers.keyAt(i);
-                num++;
-            }
+            return mUserController.getStartedUserArrayLocked();
         }
     }
 
     @Override
     public void registerUserSwitchObserver(IUserSwitchObserver observer) {
-        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: registerUserSwitchObserver() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS_FULL;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-
-        mUserSwitchObservers.register(observer);
+        mUserController.registerUserSwitchObserver(observer);
     }
 
     @Override
     public void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
-        mUserSwitchObservers.unregister(observer);
+        mUserController.unregisterUserSwitchObserver(observer);
     }
 
     int[] getUsersLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index cdb00ef..8bf1d22 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -366,7 +366,7 @@
         mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
         mWindowManager = mService.mWindowManager;
         mStackId = activityContainer.mStackId;
-        mCurrentUser = mService.mCurrentUserId;
+        mCurrentUser = mService.mUserController.mCurrentUserId;
         mRecentTasks = recentTasks;
         mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
                 ? new LaunchingTaskPositioner() : null;
@@ -901,14 +901,8 @@
         prev.task.touchActiveTime();
         clearLaunchTime(prev);
         final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
-        // In freeform mode we only update the thumbnail when there is no thumbnail yet since every
-        // focus change will request a thumbnail to be taken.
-        // Note furthermore that since windows can change their content in freeform mode all the
-        // time a thumbnail is possibly constantly outdated.
-        if (mService.mHasRecents &&
-                (next == null || next.noDisplay || next.task != prev.task || uiSleeping) &&
-                (!prev.task.hasThumbnail() ||
-                        prev.task.stack.mStackId != FREEFORM_WORKSPACE_STACK_ID)) {
+        if (mService.mHasRecents
+                && (next == null || next.noDisplay || next.task != prev.task || uiSleeping)) {
             prev.updateThumbnailLocked(screenshotActivities(prev), null);
         }
         stopFullyDrawnTraceIfNeeded();
@@ -999,10 +993,13 @@
                         r.userId, System.identityHashCode(r), r.shortComponentName,
                         mPausingActivity != null
                             ? mPausingActivity.shortComponentName : "(none)");
-                if (r.finishing && r.state == ActivityState.PAUSING) {
-                    if (DEBUG_PAUSE) Slog.v(TAG,
-                            "Executing finish of failed to pause activity: " + r);
-                    finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false);
+                if (r.state == ActivityState.PAUSING) {
+                    r.state = ActivityState.PAUSED;
+                    if (r.finishing) {
+                        if (DEBUG_PAUSE) Slog.v(TAG,
+                                "Executing finish of failed to pause activity: " + r);
+                        finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false);
+                    }
                 }
             }
         }
@@ -1381,6 +1378,20 @@
         return true;
     }
 
+    final int rankTaskLayers(int baseLayer) {
+        int layer = 0;
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            ActivityRecord r = task.topRunningActivityLocked();
+            if (r == null || r.finishing || !r.visible) {
+                task.mLayerRank = -1;
+            } else {
+                task.mLayerRank = baseLayer + layer++;
+            }
+        }
+        return layer;
+    }
+
     /**
      * Make sure that all activities that need to be visible (that is, they
      * currently can be seen by the user) actually are.
@@ -1808,7 +1819,7 @@
         // Make sure that the user who owns this activity is started.  If not,
         // we will just leave it as is because someone should be bringing
         // another user's activities to the top of the stack.
-        if (mService.mStartedUsers.get(next.userId) == null) {
+        if (!mService.mUserController.hasStartedUserState(next.userId)) {
             Slog.w(TAG, "Skipping resume of top activity " + next
                     + ": user " + next.userId + " is stopped");
             if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
@@ -3787,6 +3798,7 @@
                 task.mLastTimeMoved *= -1;
             }
         }
+        mStackSupervisor.invalidateTaskLayers();
     }
 
     void moveHomeStackTaskToTop(int homeStackTaskType) {
@@ -4572,7 +4584,7 @@
         // add the task to stack first, mTaskPositioner might need the stack association
         addTask(task, toTop, false);
         if (mTaskPositioner != null) {
-            mTaskPositioner.updateDefaultBounds(task, mTaskHistory, info.initialLayout);
+            mTaskPositioner.updateDefaultBounds(task, mTaskHistory, info.layout);
         } else if (mBounds != null && task.mResizeable) {
             task.updateOverrideConfiguration(mBounds);
         }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 3cb9887..99f7ec6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -352,6 +352,13 @@
     private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
 
+    // The default minimal size that will be used if the activity doesn't specify its minimal size.
+    // It will be calculated when the default display gets added.
+    private int mDefaultMinimalSizeOfResizeableTask = -1;
+
+    // Whether tasks have moved and we need to rank the tasks before next OOM scoring
+    private boolean mTaskLayersChanged = true;
+
     /**
      * Description of a request to start a new activity, which has been held
      * due to app switches being disabled.
@@ -432,6 +439,7 @@
                     throw new IllegalStateException("Default Display does not exist");
                 }
                 mActivityDisplays.put(displayId, activityDisplay);
+                calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
             }
 
             createStackOnDisplay(HOME_STACK_ID, Display.DEFAULT_DISPLAY, true);
@@ -916,10 +924,6 @@
                     mService.setDebugApp(aInfo.processName, true, false);
                 }
 
-                if ((startFlags & ActivityManager.START_FLAG_OPENGL_TRACES) != 0) {
-                    mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName);
-                }
-
                 if ((startFlags & ActivityManager.START_FLAG_TRACK_ALLOCATION) != 0) {
                     mService.setTrackAllocationApp(aInfo.applicationInfo, aInfo.processName);
                 }
@@ -2537,8 +2541,13 @@
     final void doPendingActivityLaunchesLocked(boolean doResume) {
         while (!mPendingActivityLaunches.isEmpty()) {
             PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
-            startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
-                    doResume && mPendingActivityLaunches.isEmpty(), null, null);
+
+            try {
+                startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
+                                             doResume && mPendingActivityLaunches.isEmpty(), null, null);
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception during pending activity launch pal=" + pal, e);
+            }
         }
     }
 
@@ -2683,7 +2692,7 @@
             // Complete user switch
             if (startingUsers != null) {
                 for (int i = 0; i < startingUsers.size(); i++) {
-                    mService.finishUserSwitch(startingUsers.get(i));
+                    mService.mUserController.finishUserSwitch(startingUsers.get(i));
                 }
             }
             // Complete starting up of background users
@@ -2691,7 +2700,7 @@
                 startingUsers = new ArrayList<UserState>(mStartingBackgroundUsers);
                 mStartingBackgroundUsers.clear();
                 for (int i = 0; i < startingUsers.size(); i++) {
-                    mService.finishUserBoot(startingUsers.get(i));
+                    mService.mUserController.finishUserBoot(startingUsers.get(i));
                 }
             }
         }
@@ -3075,6 +3084,8 @@
             return;
         }
 
+        adjustForMinimalTaskDimensions(task, bounds);
+
         // If this is a forced resize, let it go through even if the bounds is not changing,
         // as we might need a relayout due to surface size change (to/from fullscreen).
         final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
@@ -3123,6 +3134,38 @@
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
+    private void adjustForMinimalTaskDimensions(TaskRecord task, Rect bounds) {
+        if (bounds == null) {
+            return;
+        }
+        int minimalSize = task.mMinimalSize == -1 ? mDefaultMinimalSizeOfResizeableTask
+                : task.mMinimalSize;
+        final boolean adjustWidth = minimalSize > bounds.width();
+        final boolean adjustHeight = minimalSize > bounds.height();
+        if (!(adjustWidth || adjustHeight)) {
+            return;
+        }
+        Rect taskBounds = task.mBounds;
+        if (adjustWidth) {
+            if (taskBounds != null && bounds.right == taskBounds.right) {
+                bounds.left = bounds.right - minimalSize;
+            } else {
+                // Either left bounds match, or neither match, or the previous bounds were
+                // fullscreen and we default to keeping left.
+                bounds.right = bounds.left + minimalSize;
+            }
+        }
+        if (adjustHeight) {
+            if (taskBounds != null && bounds.bottom == taskBounds.bottom) {
+                bounds.top = bounds.bottom - minimalSize;
+            } else {
+                // Either top bounds match, or neither match, or the previous bounds were
+                // fullscreen and we default to keeping top.
+                bounds.bottom = bounds.top + minimalSize;
+            }
+        }
+    }
+
     ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
         ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
         if (activityDisplay == null) {
@@ -3572,6 +3615,24 @@
         }
     }
 
+    void invalidateTaskLayers() {
+        mTaskLayersChanged = true;
+    }
+
+    void rankTaskLayersIfNeeded() {
+        if (!mTaskLayersChanged) {
+            return;
+        }
+        mTaskLayersChanged = false;
+        for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); displayNdx++) {
+            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+            int baseLayer = 0;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                baseLayer += stacks.get(stackNdx).rankTaskLayers(baseLayer);
+            }
+        }
+    }
+
     void clearOtherAppTimeTrackers(AppTimeTracker except) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -3694,10 +3755,7 @@
     /** Checks whether the userid is a profile of the current user. */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUser) return true;
-        for (int i = 0; i < mService.mCurrentProfileIds.length; i++) {
-            if (mService.mCurrentProfileIds[i] == userId) return true;
-        }
-        return false;
+        return mService.mUserController.isCurrentProfileLocked(userId);
     }
 
     /** Checks whether the activity should be shown for current user. */
@@ -4033,6 +4091,7 @@
                     return;
                 }
                 mActivityDisplays.put(displayId, activityDisplay);
+                calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
             }
         }
         if (newDisplay) {
@@ -4040,6 +4099,16 @@
         }
     }
 
+    private void calculateDefaultMinimalSizeOfResizeableTasks(ActivityDisplay display) {
+        if (display.mDisplayId != Display.DEFAULT_DISPLAY) {
+            return;
+        }
+        final float fraction = mService.mContext.getResources().getFraction(com.android.internal.R.
+                fraction.config_displayFractionForDefaultMinimalSizeOfResizeableTask, 1, 1);
+        mDefaultMinimalSizeOfResizeableTask = (int) (fraction * Math.min(
+                display.mDisplayInfo.logicalWidth, display.mDisplayInfo.logicalHeight));
+    }
+
     private void handleDisplayRemoved(int displayId) {
         synchronized (mService) {
             ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 62768c3..c7228ce 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -834,10 +834,10 @@
     }
 
     @Override
-    public void noteDeviceIdleMode(boolean enabled, String activeReason, int activeUid) {
+    public void noteDeviceIdleMode(int mode, String activeReason, int activeUid) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteDeviceIdleModeLocked(enabled, activeReason, activeUid);
+            mStats.noteDeviceIdleModeLocked(mode, activeReason, activeUid);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 960cbf1..6de8579 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -44,6 +44,7 @@
 import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.TimeUtils;
 import com.android.server.DeviceIdleController;
 
 import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -1284,6 +1285,7 @@
 
     final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0
                 || mPendingBroadcast != null) {
             boolean printed = false;
@@ -1301,7 +1303,7 @@
                     pw.println("  Active broadcasts [" + mQueueName + "]:");
                 }
                 pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
-                br.dump(pw, "    ");
+                br.dump(pw, "    ", sdf);
             }
             printed = false;
             needSep = true;
@@ -1319,7 +1321,7 @@
                     pw.println("  Active ordered broadcasts [" + mQueueName + "]:");
                 }
                 pw.println("  Active Ordered Broadcast " + mQueueName + " #" + i + ":");
-                mOrderedBroadcasts.get(i).dump(pw, "    ");
+                mOrderedBroadcasts.get(i).dump(pw, "    ", sdf);
             }
             if (dumpPackage == null || (mPendingBroadcast != null
                     && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
@@ -1328,7 +1330,7 @@
                 }
                 pw.println("  Pending broadcast [" + mQueueName + "]:");
                 if (mPendingBroadcast != null) {
-                    mPendingBroadcast.dump(pw, "    ");
+                    mPendingBroadcast.dump(pw, "    ", sdf);
                 } else {
                     pw.println("    (null)");
                 }
@@ -1366,7 +1368,7 @@
             if (dumpAll) {
                 pw.print("  Historical Broadcast " + mQueueName + " #");
                         pw.print(i); pw.println(":");
-                r.dump(pw, "    ");
+                r.dump(pw, "    ", sdf);
             } else {
                 pw.print("  #"); pw.print(i); pw.print(": "); pw.println(r);
                 pw.print("    ");
@@ -1400,7 +1402,6 @@
             }
             // done skipping; dump the remainder of the ring. 'i' is still the ordinal within
             // the overall broadcast history.
-            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             do {
                 ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
                 Intent intent = mBroadcastSummaryHistory[ringIndex];
@@ -1422,9 +1423,19 @@
                 i++;
                 pw.print("  #"); pw.print(i); pw.print(": ");
                 pw.println(intent.toShortString(false, true, true, false));
-                pw.print("    enq="); pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
-                pw.print(" disp="); pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
-                pw.print(" fin="); pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
+                pw.print("    ");
+                TimeUtils.formatDuration(mSummaryHistoryDispatchTime[ringIndex]
+                        - mSummaryHistoryEnqueueTime[ringIndex], pw);
+                pw.print(" dispatch ");
+                TimeUtils.formatDuration(mSummaryHistoryFinishTime[ringIndex]
+                        - mSummaryHistoryDispatchTime[ringIndex], pw);
+                pw.println(" finish");
+                pw.print("    enq=");
+                pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
+                pw.print(" disp=");
+                pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
+                pw.print(" fin=");
+                pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
                 Bundle bundle = intent.getExtras();
                 if (bundle != null) {
                     pw.print("    extras: "); pw.println(bundle.toString());
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 1fbfd9f..b42bcff 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -32,6 +32,7 @@
 import android.util.TimeUtils;
 
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
@@ -88,7 +89,7 @@
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // info about the receiver that is currently running.
 
-    void dump(PrintWriter pw, String prefix) {
+    void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
         final long now = SystemClock.uptimeMillis();
 
         pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId);
@@ -114,13 +115,19 @@
             pw.print(prefix); pw.print("options="); pw.println(options.toBundle());
         }
         pw.print(prefix); pw.print("enqueueClockTime=");
-                pw.print(new Date(enqueueClockTime));
+                pw.print(sdf.format(new Date(enqueueClockTime)));
                 pw.print(" dispatchClockTime=");
-                pw.println(new Date(dispatchClockTime));
+                pw.println(sdf.format(new Date(dispatchClockTime)));
         pw.print(prefix); pw.print("dispatchTime=");
                 TimeUtils.formatDuration(dispatchTime, now, pw);
+                pw.print(" (");
+                TimeUtils.formatDuration(dispatchClockTime-enqueueClockTime, pw);
+                pw.print(" since enq)");
         if (finishTime != 0) {
             pw.print(" finishTime="); TimeUtils.formatDuration(finishTime, now, pw);
+            pw.print(" (");
+            TimeUtils.formatDuration(finishTime-dispatchTime, pw);
+            pw.print(" since disp)");
         } else {
             pw.print(" receiverTime="); TimeUtils.formatDuration(receiverTime, now, pw);
         }
diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
index 3d45915..4ba1d0d 100644
--- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
+++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
@@ -109,22 +109,22 @@
      *
      * @param task Task for which we want to find bounds that won't collide with other.
      * @param tasks Existing tasks with which we don't want to collide.
-     * @param initialLayout Optional information from the client about how it would like to be sized
+     * @param layout Optional information from the client about how it would like to be sized
      *                      and positioned.
      */
     void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks,
-            @Nullable ActivityInfo.InitialLayout initialLayout) {
+            @Nullable ActivityInfo.Layout layout) {
         if (!mDefaultStartBoundsConfigurationSet) {
             return;
         }
-        if (initialLayout == null) {
+        if (layout == null) {
             positionCenter(task, tasks, mDefaultFreeformWidth, mDefaultFreeformHeight);
             return;
         }
-        int width = getFinalWidth(initialLayout);
-        int height = getFinalHeight(initialLayout);
-        int verticalGravity = initialLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
-        int horizontalGravity = initialLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        int width = getFinalWidth(layout);
+        int height = getFinalHeight(layout);
+        int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
         if (verticalGravity == Gravity.TOP) {
             if (horizontalGravity == Gravity.RIGHT) {
                 positionTopRight(task, tasks, width, height);
@@ -140,30 +140,30 @@
         } else {
             // Some fancy gravity setting that we don't support yet. We just put the activity in the
             // center.
-            Slog.w(TAG, "Received unsupported gravity: " + initialLayout.gravity
+            Slog.w(TAG, "Received unsupported gravity: " + layout.gravity
                     + ", positioning in the center instead.");
             positionCenter(task, tasks, width, height);
         }
     }
 
-    private int getFinalWidth(ActivityInfo.InitialLayout initialLayout) {
+    private int getFinalWidth(ActivityInfo.Layout layout) {
         int width = mDefaultFreeformWidth;
-        if (initialLayout.width > 0) {
-            width = initialLayout.width;
+        if (layout.width > 0) {
+            width = layout.width;
         }
-        if (initialLayout.widthFraction > 0) {
-            width = (int) (mAvailableRect.width() * initialLayout.widthFraction);
+        if (layout.widthFraction > 0) {
+            width = (int) (mAvailableRect.width() * layout.widthFraction);
         }
         return width;
     }
 
-    private int getFinalHeight(ActivityInfo.InitialLayout initialLayout) {
+    private int getFinalHeight(ActivityInfo.Layout layout) {
         int height = mDefaultFreeformHeight;
-        if (initialLayout.height > 0) {
-            height = initialLayout.height;
+        if (layout.height > 0) {
+            height = layout.height;
         }
-        if (initialLayout.heightFraction > 0) {
-            height = (int) (mAvailableRect.height() * initialLayout.heightFraction);
+        if (layout.heightFraction > 0) {
+            height = (int) (mAvailableRect.height() * layout.heightFraction);
         }
         return height;
     }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 5b46799..6ed880e 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -248,7 +248,7 @@
                 boolean sendFinish = finishedReceiver != null;
                 int userId = key.userId;
                 if (userId == UserHandle.USER_CURRENT) {
-                    userId = owner.getCurrentUserIdLocked();
+                    userId = owner.mUserController.getCurrentUserIdLocked();
                 }
                 switch (key.type) {
                     case ActivityManager.INTENT_SENDER_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0e24952..b49370b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -49,19 +49,22 @@
 
     // OOM adjustments for processes in various states:
 
+    // Uninitialized value for any major or minor adj fields
+    static final int INVALID_ADJ = -10000;
+
     // Adjustment used in certain places where we don't know it yet.
     // (Generally this is something that is going to be cached, but we
     // don't know the exact value in the cached range to assign yet.)
-    static final int UNKNOWN_ADJ = 16;
+    static final int UNKNOWN_ADJ = 1001;
 
     // This is a process only hosting activities that are not visible,
     // so it can be killed without any disruption.
-    static final int CACHED_APP_MAX_ADJ = 15;
-    static final int CACHED_APP_MIN_ADJ = 9;
+    static final int CACHED_APP_MAX_ADJ = 906;
+    static final int CACHED_APP_MIN_ADJ = 900;
 
     // The B list of SERVICE_ADJ -- these are the old and decrepit
     // services that aren't as shiny and interesting as the ones in the A list.
-    static final int SERVICE_B_ADJ = 8;
+    static final int SERVICE_B_ADJ = 800;
 
     // This is the process of the previous application that the user was in.
     // This process is kept above other things, because it is very common to
@@ -69,34 +72,35 @@
     // task switch (toggling between the two top recent apps) as well as normal
     // UI flow such as clicking on a URI in the e-mail app to view in the browser,
     // and then pressing back to return to e-mail.
-    static final int PREVIOUS_APP_ADJ = 7;
+    static final int PREVIOUS_APP_ADJ = 700;
 
     // This is a process holding the home application -- we want to try
     // avoiding killing it, even if it would normally be in the background,
     // because the user interacts with it so much.
-    static final int HOME_APP_ADJ = 6;
+    static final int HOME_APP_ADJ = 600;
 
     // This is a process holding an application service -- killing it will not
     // have much of an impact as far as the user is concerned.
-    static final int SERVICE_ADJ = 5;
+    static final int SERVICE_ADJ = 500;
 
     // This is a process with a heavy-weight application.  It is in the
     // background, but we want to try to avoid killing it.  Value set in
     // system/rootdir/init.rc on startup.
-    static final int HEAVY_WEIGHT_APP_ADJ = 4;
+    static final int HEAVY_WEIGHT_APP_ADJ = 400;
 
     // This is a process currently hosting a backup operation.  Killing it
     // is not entirely fatal but is generally a bad idea.
-    static final int BACKUP_APP_ADJ = 3;
+    static final int BACKUP_APP_ADJ = 300;
 
     // This is a process only hosting components that are perceptible to the
     // user, and we really want to avoid killing them, but they are not
     // immediately visible. An example is background music playback.
-    static final int PERCEPTIBLE_APP_ADJ = 2;
+    static final int PERCEPTIBLE_APP_ADJ = 200;
 
     // This is a process only hosting activities that are visible to the
     // user, so we'd prefer they don't disappear.
-    static final int VISIBLE_APP_ADJ = 1;
+    static final int VISIBLE_APP_ADJ = 100;
+    static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1;
 
     // This is the process running the current foreground app.  We'd really
     // rather not kill it!
@@ -104,18 +108,18 @@
 
     // This is a process that the system or a persistent process has bound to,
     // and indicated it is important.
-    static final int PERSISTENT_SERVICE_ADJ = -11;
+    static final int PERSISTENT_SERVICE_ADJ = -700;
 
     // This is a system persistent process, such as telephony.  Definitely
     // don't want to kill it, but doing so is not completely fatal.
-    static final int PERSISTENT_PROC_ADJ = -12;
+    static final int PERSISTENT_PROC_ADJ = -800;
 
     // The system process runs at the default adjustment.
-    static final int SYSTEM_ADJ = -16;
+    static final int SYSTEM_ADJ = -900;
 
     // Special code for native processes that are not being managed by the system (so
     // don't have an oom adj assigned by the system).
-    static final int NATIVE_ADJ = -17;
+    static final int NATIVE_ADJ = -1000;
 
     // Memory pages are 4K.
     static final int PAGE_SIZE = 4*1024;
@@ -159,7 +163,7 @@
     // These must be kept in sync with the definitions in lmkd.c
     //
     // LMK_TARGET <minfree> <minkillprio> ... (up to 6 pairs)
-    // LMK_PROCPRIO <pid> <prio>
+    // LMK_PROCPRIO <pid> <uid> <prio>
     // LMK_PROCREMOVE <pid>
     static final byte LMK_TARGET = 0;
     static final byte LMK_PROCPRIO = 1;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index bd31a21..08203c55b 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -171,10 +171,10 @@
     boolean debugging;          // was app launched for debugging?
     boolean waitedForDebugger;  // has process show wait for debugger dialog?
     Dialog waitDialog;          // current wait for debugger dialog
-    
+
     String shortStringName;     // caching of toShortString() result.
     String stringName;          // caching of toString() result.
-    
+
     // These reports are generated & stored when an app gets into an error condition.
     // They will be "null" when all is OK.
     ActivityManager.ProcessErrorStateInfo crashingReport;
@@ -402,7 +402,7 @@
             }
         }
     }
-    
+
     ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info,
             String _processName, int _uid) {
         mBatteryStats = _batteryStats;
@@ -413,8 +413,8 @@
         processName = _processName;
         pkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.versionCode));
         maxAdj = ProcessList.UNKNOWN_ADJ;
-        curRawAdj = setRawAdj = -100;
-        curAdj = setAdj = -100;
+        curRawAdj = setRawAdj = ProcessList.INVALID_ADJ;
+        curAdj = setAdj = ProcessList.INVALID_ADJ;
         persistent = false;
         removed = false;
         lastStateTime = lastPssTime = nextPssTime = SystemClock.uptimeMillis();
@@ -560,7 +560,7 @@
         toShortString(sb);
         return shortStringName = sb.toString();
     }
-    
+
     void toShortString(StringBuilder sb) {
         sb.append(pid);
         sb.append(':');
@@ -585,7 +585,7 @@
             }
         }
     }
-    
+
     public String toString() {
         if (stringName != null) {
             return stringName;
@@ -695,7 +695,7 @@
             pkgList.put(info.packageName, new ProcessStats.ProcessStateHolder(info.versionCode));
         }
     }
-    
+
     public String[] getPackageList() {
         int size = pkgList.size();
         if (size == 0) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 77dbad4..fe87a93 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -217,6 +217,13 @@
     // The information is persisted and used to determine the appropriate stack to launch the
     // task into on restore.
     Rect mLastNonFullscreenBounds = null;
+    // Minimal size for width/height of this task when it's resizeable. -1 means it should use the
+    // default minimal size.
+    final int mMinimalSize;
+
+    // Ranking (from top) of this task among all visible tasks. (-1 means it's not visible)
+    // This number will be assigned when we evaluate OOM scores for all visible tasks.
+    int mLayerRank = -1;
 
     Configuration mOverrideConfig = Configuration.EMPTY;
 
@@ -235,6 +242,7 @@
         mCallingUid = info.applicationInfo.uid;
         mCallingPackage = info.packageName;
         setIntent(_intent, info);
+        mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
     }
 
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
@@ -263,6 +271,7 @@
         mTaskToReturnTo = HOME_ACTIVITY_TYPE;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         lastTaskDescription = _taskDescription;
+        mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
     }
 
     private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent,
@@ -310,6 +319,8 @@
         mCallingPackage = callingPackage;
         mResizeable = resizeable;
         mPrivileged = privileged;
+        ActivityInfo info = mActivities.get(0).info;
+        mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
     }
 
     void touchActiveTime() {
@@ -478,14 +489,6 @@
     }
 
     /**
-     * Returns true when we have a thumbnail.
-     * @return Returns true if there is a thumbnail.
-     */
-    boolean hasThumbnail() {
-        return mLastThumbnail != null;
-    }
-
-    /**
      * Sets the last thumbnail.
      * @return whether the thumbnail was set
      */
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
new file mode 100644
index 0000000..ff74d83
--- /dev/null
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -0,0 +1,862 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.ActivityManager.USER_OP_IS_CURRENT;
+import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
+import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IStopUserCallback;
+import android.app.IUserSwitchObserver;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.UserManagerService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
+ */
+final class UserController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
+    // Maximum number of users we allow to be running at a time.
+    static final int MAX_RUNNING_USERS = 3;
+
+    // Amount of time we wait for observers to handle a user switch before
+    // giving up on them and unfreezing the screen.
+    static final int USER_SWITCH_TIMEOUT = 2 * 1000;
+
+    private final ActivityManagerService mService;
+    private final Handler mHandler;
+
+    // Holds the current foreground user's id
+    int mCurrentUserId = 0;
+    // Holds the target user's id during a user switch
+    int mTargetUserId = UserHandle.USER_NULL;
+
+    /**
+     * Which users have been started, so are allowed to run code.
+     */
+    private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
+    /**
+     * LRU list of history of current users.  Most recently current is at the end.
+     */
+    private final ArrayList<Integer> mUserLru = new ArrayList<>();
+
+    /**
+     * Constant array of the users that are currently started.
+     */
+    private int[] mStartedUserArray = new int[] { 0 };
+
+    // If there are multiple profiles for the current user, their ids are here
+    // Currently only the primary user can have managed profiles
+    int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
+
+    /**
+     * Mapping from each known user ID to the profile group ID it is associated with.
+     */
+    private final SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+
+    /**
+     * Registered observers of the user switching mechanics.
+     */
+    final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+            = new RemoteCallbackList<>();
+
+    /**
+     * Currently active user switch.
+     */
+    Object mCurUserSwitchCallback;
+
+    UserController(ActivityManagerService service) {
+        mService = service;
+        mHandler = mService.mHandler;
+        // User 0 is the first and only user that runs at boot.
+        mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
+        mUserLru.add(UserHandle.USER_SYSTEM);
+        updateStartedUserArrayLocked();
+    }
+
+    void finishUserSwitch(UserState uss) {
+        synchronized (mService) {
+            finishUserBoot(uss);
+
+            startProfilesLocked();
+
+            int num = mUserLru.size();
+            int i = 0;
+            while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
+                Integer oldUserId = mUserLru.get(i);
+                UserState oldUss = mStartedUsers.get(oldUserId);
+                if (oldUss == null) {
+                    // Shouldn't happen, but be sane if it does.
+                    mUserLru.remove(i);
+                    num--;
+                    continue;
+                }
+                if (oldUss.mState == UserState.STATE_STOPPING
+                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
+                    // This user is already stopping, doesn't count.
+                    num--;
+                    i++;
+                    continue;
+                }
+                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
+                    // Owner/System user and current user can't be stopped. We count it as running
+                    // when it is not a pure system user.
+                    if (UserInfo.isSystemOnly(oldUserId)) {
+                        num--;
+                    }
+                    i++;
+                    continue;
+                }
+                // This is a user to be stopped.
+                stopUserLocked(oldUserId, null);
+                num--;
+                i++;
+            }
+        }
+    }
+
+    void finishUserBoot(UserState uss) {
+        synchronized (mService) {
+            if (uss.mState == UserState.STATE_BOOTING
+                    && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
+                uss.mState = UserState.STATE_RUNNING;
+                final int userId = uss.mHandle.getIdentifier();
+                Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+                mService.broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null,
+                        new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                        AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
+                        Process.SYSTEM_UID, userId);
+            }
+        }
+    }
+
+    int stopUser(final int userId, final IStopUserCallback callback) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: switchUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
+            throw new IllegalArgumentException("Can't stop system user " + userId);
+        }
+        mService.enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
+                userId);
+        synchronized (mService) {
+            return stopUserLocked(userId, callback);
+        }
+    }
+
+    private int stopUserLocked(final int userId, final IStopUserCallback callback) {
+        if (DEBUG_MU) Slog.i(TAG, "stopUserLocked userId=" + userId);
+        if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
+            return USER_OP_IS_CURRENT;
+        }
+
+        final UserState uss = mStartedUsers.get(userId);
+        if (uss == null) {
+            // User is not started, nothing to do...  but we do need to
+            // callback if requested.
+            if (callback != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            callback.userStopped(userId);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                });
+            }
+            return USER_OP_SUCCESS;
+        }
+
+        if (callback != null) {
+            uss.mStopCallbacks.add(callback);
+        }
+
+        if (uss.mState != UserState.STATE_STOPPING
+                && uss.mState != UserState.STATE_SHUTDOWN) {
+            uss.mState = UserState.STATE_STOPPING;
+            updateStartedUserArrayLocked();
+
+            long ident = Binder.clearCallingIdentity();
+            try {
+                // We are going to broadcast ACTION_USER_STOPPING and then
+                // once that is done send a final ACTION_SHUTDOWN and then
+                // stop the user.
+                final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
+                stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
+                final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+                // This is the result receiver for the final shutdown broadcast.
+                final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                        finishUserStop(uss);
+                    }
+                };
+                // This is the result receiver for the initial stopping broadcast.
+                final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                        // On to the next.
+                        synchronized (mService) {
+                            if (uss.mState != UserState.STATE_STOPPING) {
+                                // Whoops, we are being started back up.  Abort, abort!
+                                return;
+                            }
+                            uss.mState = UserState.STATE_SHUTDOWN;
+                        }
+                        mService.mBatteryStatsService.noteEvent(
+                                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
+                                Integer.toString(userId), userId);
+                        mService.mSystemServiceManager.stopUser(userId);
+                        mService.broadcastIntentLocked(null, null, shutdownIntent,
+                                null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
+                                null, true, false, ActivityManagerService.MY_PID,
+                                android.os.Process.SYSTEM_UID, userId);
+                    }
+                };
+                // Kick things off.
+                mService.broadcastIntentLocked(null, null, stoppingIntent,
+                        null, stoppingReceiver, 0, null, null,
+                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                        null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                        UserHandle.USER_ALL);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        return USER_OP_SUCCESS;
+    }
+
+    void finishUserStop(UserState uss) {
+        final int userId = uss.mHandle.getIdentifier();
+        boolean stopped;
+        ArrayList<IStopUserCallback> callbacks;
+        synchronized (mService) {
+            callbacks = new ArrayList<>(uss.mStopCallbacks);
+            if (mStartedUsers.get(userId) != uss) {
+                stopped = false;
+            } else if (uss.mState != UserState.STATE_SHUTDOWN) {
+                stopped = false;
+            } else {
+                stopped = true;
+                // User can no longer run.
+                mStartedUsers.remove(userId);
+                mUserLru.remove(Integer.valueOf(userId));
+                updateStartedUserArrayLocked();
+
+                // Clean up all state and processes associated with the user.
+                // Kill all the processes for the user.
+                forceStopUserLocked(userId, "finish user");
+            }
+        }
+
+        for (int i = 0; i < callbacks.size(); i++) {
+            try {
+                if (stopped) callbacks.get(i).userStopped(userId);
+                else callbacks.get(i).userStopAborted(userId);
+            } catch (RemoteException e) {
+            }
+        }
+
+        if (stopped) {
+            mService.mSystemServiceManager.cleanupUser(userId);
+            synchronized (mService) {
+                mService.mStackSupervisor.removeUserLocked(userId);
+            }
+        }
+    }
+
+    private void forceStopUserLocked(int userId, String reason) {
+        mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+                userId, reason);
+        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        mService.broadcastIntentLocked(null, null, intent,
+                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                UserHandle.USER_ALL);
+    }
+
+
+    /**
+     * Stops the guest user if it has gone to the background.
+     */
+    private void stopGuestUserIfBackground() {
+        synchronized (mService) {
+            final int num = mUserLru.size();
+            for (int i = 0; i < num; i++) {
+                Integer oldUserId = mUserLru.get(i);
+                UserState oldUss = mStartedUsers.get(oldUserId);
+                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
+                        || oldUss.mState == UserState.STATE_STOPPING
+                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
+                    continue;
+                }
+                UserInfo userInfo = getUserManagerLocked().getUserInfo(oldUserId);
+                if (userInfo.isGuest()) {
+                    // This is a user to be stopped.
+                    stopUserLocked(oldUserId, null);
+                    break;
+                }
+            }
+        }
+    }
+
+    void startProfilesLocked() {
+        if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
+        List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+                mCurrentUserId, false /* enabledOnly */);
+        List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
+        for (UserInfo user : profiles) {
+            if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
+                    && user.id != mCurrentUserId) {
+                profilesToStart.add(user);
+            }
+        }
+        final int profilesToStartSize = profilesToStart.size();
+        int i = 0;
+        for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) {
+            startUser(profilesToStart.get(i).id, /* foreground= */ false);
+        }
+        if (i < profilesToStartSize) {
+            Slog.w(TAG, "More profiles than MAX_RUNNING_USERS");
+        }
+    }
+
+    private UserManagerService getUserManagerLocked() {
+        return mService.getUserManagerLocked();
+    }
+
+    boolean startUser(final int userId, final boolean foreground) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: switchUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        if (DEBUG_MU) Slog.i(TAG, "starting userid:" + userId + " fore:" + foreground);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mService) {
+                final int oldUserId = mCurrentUserId;
+                if (oldUserId == userId) {
+                    return true;
+                }
+
+                mService.mStackSupervisor.setLockTaskModeLocked(null,
+                        ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
+
+                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+                if (userInfo == null) {
+                    Slog.w(TAG, "No user info for user #" + userId);
+                    return false;
+                }
+                if (foreground && userInfo.isManagedProfile()) {
+                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+                    return false;
+                }
+
+                if (foreground) {
+                    mService.mWindowManager.startFreezingScreen(
+                            R.anim.screen_user_exit, R.anim.screen_user_enter);
+                }
+
+                boolean needStart = false;
+
+                // If the user we are switching to is not currently started, then
+                // we need to start it now.
+                if (mStartedUsers.get(userId) == null) {
+                    mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                }
+
+                final Integer userIdInt = userId;
+                mUserLru.remove(userIdInt);
+                mUserLru.add(userIdInt);
+
+                if (foreground) {
+                    mCurrentUserId = userId;
+                    mService.updateUserConfigurationLocked();
+                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+                    updateCurrentProfileIdsLocked();
+                    mService.mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
+                    // Once the internal notion of the active user has switched, we lock the device
+                    // with the option to show the user switcher on the keyguard.
+                    mService.mWindowManager.lockNow(null);
+                } else {
+                    final Integer currentUserIdInt = mCurrentUserId;
+                    updateCurrentProfileIdsLocked();
+                    mService.mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
+                    mUserLru.remove(currentUserIdInt);
+                    mUserLru.add(currentUserIdInt);
+                }
+
+                final UserState uss = mStartedUsers.get(userId);
+
+                // Make sure user is in the started state.  If it is currently
+                // stopping, we need to knock that off.
+                if (uss.mState == UserState.STATE_STOPPING) {
+                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+                    // so we can just fairly silently bring the user back from
+                    // the almost-dead.
+                    uss.mState = UserState.STATE_RUNNING;
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                } else if (uss.mState == UserState.STATE_SHUTDOWN) {
+                    // This means ACTION_SHUTDOWN has been sent, so we will
+                    // need to treat this as a new boot of the user.
+                    uss.mState = UserState.STATE_BOOTING;
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                }
+
+                if (uss.mState == UserState.STATE_BOOTING) {
+                    // Booting up a new user, need to tell system services about it.
+                    // Note that this is on the same handler as scheduling of broadcasts,
+                    // which is important because it needs to go first.
+                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+                }
+
+                if (foreground) {
+                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+                            oldUserId));
+                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+                            oldUserId, userId, uss));
+                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
+                }
+
+                if (needStart) {
+                    // Send USER_STARTED broadcast
+                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                    mService.broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                            userId);
+                }
+
+                if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+                    if (userId != UserHandle.USER_SYSTEM) {
+                        Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+                        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                        mService.broadcastIntentLocked(null, null, intent, null,
+                                new IIntentReceiver.Stub() {
+                                    public void performReceive(Intent intent, int resultCode,
+                                            String data, Bundle extras, boolean ordered,
+                                            boolean sticky, int sendingUser) {
+                                        onUserInitialized(uss, foreground, oldUserId, userId);
+                                    }
+                                }, 0, null, null, null, AppOpsManager.OP_NONE,
+                                null, true, false, ActivityManagerService.MY_PID,
+                                Process.SYSTEM_UID, userId);
+                        uss.initializing = true;
+                    } else {
+                        getUserManagerLocked().makeInitialized(userInfo.id);
+                    }
+                }
+
+                if (foreground) {
+                    if (!uss.initializing) {
+                        moveUserToForegroundLocked(uss, oldUserId, userId);
+                    }
+                } else {
+                    mService.mStackSupervisor.startBackgroundUserLocked(userId, uss);
+                }
+
+                if (needStart) {
+                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                    mService.broadcastIntentLocked(null, null, intent,
+                            null, new IIntentReceiver.Stub() {
+                                @Override
+                                public void performReceive(Intent intent, int resultCode,
+                                        String data, Bundle extras, boolean ordered, boolean sticky,
+                                        int sendingUser) throws RemoteException {
+                                }
+                            }, 0, null, null,
+                            new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                            null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                            UserHandle.USER_ALL);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return true;
+    }
+
+    void dispatchForegroundProfileChanged(int userId) {
+        final int observerCount = mUserSwitchObservers.beginBroadcast();
+        for (int i = 0; i < observerCount; i++) {
+            try {
+                mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    /** Called on handler thread */
+    void dispatchUserSwitchComplete(int userId) {
+        final int observerCount = mUserSwitchObservers.beginBroadcast();
+        for (int i = 0; i < observerCount; i++) {
+            try {
+                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
+            } catch (RemoteException e) {
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+        synchronized (mService) {
+            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+        }
+    }
+
+    void dispatchUserSwitch(final UserState uss, final int oldUserId,
+            final int newUserId) {
+        final int observerCount = mUserSwitchObservers.beginBroadcast();
+        if (observerCount > 0) {
+            final IRemoteCallback callback = new IRemoteCallback.Stub() {
+                int mCount = 0;
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    synchronized (mService) {
+                        if (mCurUserSwitchCallback == this) {
+                            mCount++;
+                            if (mCount == observerCount) {
+                                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                            }
+                        }
+                    }
+                }
+            };
+            synchronized (mService) {
+                uss.switching = true;
+                mCurUserSwitchCallback = callback;
+            }
+            for (int i = 0; i < observerCount; i++) {
+                try {
+                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
+                            newUserId, callback);
+                } catch (RemoteException e) {
+                }
+            }
+        } else {
+            synchronized (mService) {
+                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
+        mCurUserSwitchCallback = null;
+        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+        mHandler.sendMessage(mHandler.obtainMessage(ActivityManagerService.CONTINUE_USER_SWITCH_MSG,
+                oldUserId, newUserId, uss));
+    }
+
+    void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
+        completeSwitchAndInitialize(uss, newUserId, false, true);
+    }
+
+    void onUserInitialized(UserState uss, boolean foreground, int oldUserId, int newUserId) {
+        synchronized (mService) {
+            if (foreground) {
+                moveUserToForegroundLocked(uss, oldUserId, newUserId);
+            }
+        }
+        completeSwitchAndInitialize(uss, newUserId, true, false);
+    }
+
+    void completeSwitchAndInitialize(UserState uss, int newUserId,
+            boolean clearInitializing, boolean clearSwitching) {
+        boolean unfrozen = false;
+        synchronized (mService) {
+            if (clearInitializing) {
+                uss.initializing = false;
+                getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
+            }
+            if (clearSwitching) {
+                uss.switching = false;
+            }
+            if (!uss.switching && !uss.initializing) {
+                mService.mWindowManager.stopFreezingScreen();
+                unfrozen = true;
+            }
+        }
+        if (unfrozen) {
+            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+            mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
+                    newUserId, 0));
+        }
+        stopGuestUserIfBackground();
+    }
+
+    void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
+        boolean homeInFront = mService.mStackSupervisor.switchUserLocked(newUserId, uss);
+        if (homeInFront) {
+            mService.startHomeActivityLocked(newUserId, "moveUserToForeground");
+        } else {
+            mService.mStackSupervisor.resumeTopActivitiesLocked();
+        }
+        EventLogTags.writeAmSwitchUser(newUserId);
+        getUserManagerLocked().onUserForeground(newUserId);
+        mService.sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+    }
+
+    void registerUserSwitchObserver(IUserSwitchObserver observer) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            final String msg = "Permission Denial: registerUserSwitchObserver() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        mUserSwitchObservers.register(observer);
+    }
+
+    void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
+        mUserSwitchObservers.unregister(observer);
+    }
+
+    UserState getStartedUserState(int userId) {
+        return mStartedUsers.get(userId);
+    }
+
+    boolean hasStartedUserState(int userId) {
+        return mStartedUsers.get(userId) != null;
+    }
+
+    private void updateStartedUserArrayLocked() {
+        int num = 0;
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            // This list does not include stopping users.
+            if (uss.mState != UserState.STATE_STOPPING
+                    && uss.mState != UserState.STATE_SHUTDOWN) {
+                num++;
+            }
+        }
+        mStartedUserArray = new int[num];
+        num = 0;
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            if (uss.mState != UserState.STATE_STOPPING
+                    && uss.mState != UserState.STATE_SHUTDOWN) {
+                mStartedUserArray[num] = mStartedUsers.keyAt(i);
+                num++;
+            }
+        }
+    }
+
+    void sendBootCompletedLocked(IIntentReceiver resultTo) {
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            if (uss.mState == UserState.STATE_BOOTING) {
+                uss.mState = UserState.STATE_RUNNING;
+                final int userId = mStartedUsers.keyAt(i);
+                Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+                mService.broadcastIntentLocked(null, null, intent, null,
+                        resultTo, 0, null, null,
+                        new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                        AppOpsManager.OP_NONE, null, true, false,
+                        ActivityManagerService.MY_PID, Process.SYSTEM_UID, userId);
+            }
+        }
+    }
+
+    /**
+     * Refreshes the list of users related to the current user when either a
+     * user switch happens or when a new related user is started in the
+     * background.
+     */
+    void updateCurrentProfileIdsLocked() {
+        final List<UserInfo> profiles = getUserManagerLocked().getProfiles(mCurrentUserId,
+                false /* enabledOnly */);
+        int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
+        for (int i = 0; i < currentProfileIds.length; i++) {
+            currentProfileIds[i] = profiles.get(i).id;
+        }
+        mCurrentProfileIds = currentProfileIds;
+
+        synchronized (mUserProfileGroupIdsSelfLocked) {
+            mUserProfileGroupIdsSelfLocked.clear();
+            final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+            for (int i = 0; i < users.size(); i++) {
+                UserInfo user = users.get(i);
+                if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+                }
+            }
+        }
+    }
+
+    int[] getStartedUserArrayLocked() {
+        return mStartedUserArray;
+    }
+
+    UserInfo getCurrentUser() {
+        if ((mService.checkCallingPermission(INTERACT_ACROSS_USERS)
+                != PackageManager.PERMISSION_GRANTED) && (
+                mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                        != PackageManager.PERMISSION_GRANTED)) {
+            String msg = "Permission Denial: getCurrentUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (mService) {
+            return getCurrentUserLocked();
+        }
+    }
+
+    UserInfo getCurrentUserLocked() {
+        int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        return getUserManagerLocked().getUserInfo(userId);
+    }
+
+    int getCurrentUserIdLocked() {
+        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+    }
+
+    boolean isSameProfileGroup(int callingUserId, int targetUserId) {
+        synchronized (mUserProfileGroupIdsSelfLocked) {
+            int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+                    UserInfo.NO_PROFILE_GROUP_ID);
+            int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+                    UserInfo.NO_PROFILE_GROUP_ID);
+            return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
+                    && callingProfile == targetProfile;
+        }
+    }
+
+    boolean isCurrentProfileLocked(int userId) {
+        return ArrayUtils.contains(mCurrentProfileIds, userId);
+    }
+
+    void dump(PrintWriter pw, boolean dumpAll) {
+        pw.println("  mStartedUsers:");
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
+            pw.print(": "); uss.dump("", pw);
+        }
+        pw.print("  mStartedUserArray: [");
+        for (int i = 0; i < mStartedUserArray.length; i++) {
+            if (i > 0) pw.print(", ");
+            pw.print(mStartedUserArray[i]);
+        }
+        pw.println("]");
+        pw.print("  mUserLru: [");
+        for (int i = 0; i < mUserLru.size(); i++) {
+            if (i > 0) pw.print(", ");
+            pw.print(mUserLru.get(i));
+        }
+        pw.println("]");
+        if (dumpAll) {
+            pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
+        }
+        synchronized (mUserProfileGroupIdsSelfLocked) {
+            if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+                pw.println("  mUserProfileGroupIds:");
+                for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+                    pw.print("    User #");
+                    pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+                    pw.print(" -> profile #");
+                    pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+                }
+            }
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index e6dc895..15e5393 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -104,6 +104,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
 
@@ -258,7 +259,8 @@
 
     private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
-            boolean idle = mPowerManager.isDeviceIdleMode();
+            boolean idle = mPowerManager.isDeviceIdleMode()
+                    || mPowerManager.isLightDeviceIdleMode();
             mDeviceIsIdle = idle;
             if (idle) {
                 cancelActiveSync(
@@ -478,6 +480,7 @@
         context.registerReceiver(mStorageIntentReceiver, intentFilter);
 
         intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        intentFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
         context.registerReceiver(mDeviceIdleReceiver, intentFilter);
 
         intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
@@ -3370,7 +3373,7 @@
             if (!smaller.containsKey(key)) {
                 return false;
             }
-            if (!bigger.get(key).equals(smaller.get(key))) {
+            if (!Objects.equals(bigger.get(key), smaller.get(key))) {
                 return false;
             }
         }
@@ -3378,7 +3381,6 @@
     }
 
     /**
-     * TODO: Get rid of this when we separate sync settings extras from dev specified extras.
      * @return true if the provided key is used by the SyncManager in scheduling the sync.
      */
     private static boolean isSyncSetting(String key) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5a13672..81ae8ac 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -206,6 +206,7 @@
     private static native void nativeReloadDeviceAliases(long ptr);
     private static native String nativeDump(long ptr);
     private static native void nativeMonitor(long ptr);
+    private static native void nativeSetPointerIconShape(long ptr, int iconId);
 
     // Input event injection constants defined in InputDispatcher.h.
     private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
@@ -792,8 +793,17 @@
     }
 
     @Override // Binder call
+    public int isInTabletMode() {
+        if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE,
+                "isInTabletMode()")) {
+            throw new SecurityException("Requires TABLET_MODE permission");
+        }
+        return getSwitchState(-1, InputDevice.SOURCE_ANY, SW_TABLET_MODE);
+    }
+
+    @Override // Binder call
     public void registerTabletModeChangedListener(ITabletModeChangedListener listener) {
-        if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE_LISTENER,
+        if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE,
                 "registerTabletModeChangedListener()")) {
             throw new SecurityException("Requires TABLET_MODE_LISTENER permission");
         }
@@ -1407,6 +1417,12 @@
         }
     }
 
+  // Binder call
+  @Override
+  public void setPointerIconShape(int iconId) {
+      nativeSetPointerIconShape(mPtr, iconId);
+  }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
@@ -1488,7 +1504,7 @@
                     switchMask);
         }
 
-        if ((switchMask & SW_TABLET_MODE) != 0) {
+        if ((switchMask & SW_TABLET_MODE_BIT) != 0) {
             SomeArgs args = SomeArgs.obtain();
             args.argi1 = (int) (whenNanos & 0xFFFFFFFF);
             args.argi2 = (int) (whenNanos >> 32);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 06bd583..2b535b9 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -73,7 +73,7 @@
  */
 public class JobSchedulerService extends com.android.server.SystemService
         implements StateChangedListener, JobCompletedListener {
-    static final boolean DEBUG = false;
+    public static final boolean DEBUG = false;
     /** The number of concurrent jobs we run at one time. */
     private static final int MAX_JOB_CONTEXTS_COUNT
             = ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
@@ -99,7 +99,7 @@
      * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
      * things early.
      */
-    static final int MIN_CONNECTIVITY_COUNT = 2;
+    static final int MIN_CONNECTIVITY_COUNT = 1;  // Run connectivity jobs as soon as ready.
     /**
      * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
      * some work early.
@@ -162,8 +162,12 @@
                     Slog.d(TAG, "Removing jobs for user: " + userId);
                 }
                 cancelJobsForUser(userId);
-            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
-                updateIdleMode(mPowerManager != null ? mPowerManager.isDeviceIdleMode() : false);
+            } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
+                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
+                updateIdleMode(mPowerManager != null
+                        ? (mPowerManager.isDeviceIdleMode()
+                                || mPowerManager.isLightDeviceIdleMode())
+                        : false);
             }
         }
     };
@@ -340,6 +344,7 @@
                     mBroadcastReceiver, UserHandle.ALL, filter, null, null);
             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
             userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+            userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
             mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index d7fafe3..5376043 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -62,7 +62,7 @@
  *
  */
 public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = JobSchedulerService.DEBUG;
     private static final String TAG = "JobServiceContext";
     /** Define the maximum # of jobs allowed to run on a service at once. */
     private static final int defaultMaxActiveJobsPerService =
@@ -109,7 +109,13 @@
     int mVerb;
     private AtomicBoolean mCancelled = new AtomicBoolean();
 
-    /** All the information maintained about the job currently being executed. */
+    /**
+     * All the information maintained about the job currently being executed.
+     *
+     * Any reads (dereferences) not done from the handler thread must be synchronized on
+     * {@link #mLock}.
+     * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
+     */
     private JobStatus mRunningJob;
     /** Binder to the client service. */
     IJobService service;
@@ -194,7 +200,8 @@
      */
     JobStatus getRunningJob() {
         synchronized (mLock) {
-            return mRunningJob;
+            return mRunningJob == null ?
+                    null : new JobStatus(mRunningJob);
         }
     }
 
@@ -255,15 +262,22 @@
      */
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
-        if (!name.equals(mRunningJob.getServiceComponent())) {
+        JobStatus runningJob;
+        synchronized (mLock) {
+            // This isn't strictly necessary b/c the JobServiceHandler is running on the main
+            // looper and at this point we can't get any binder callbacks from the client. Better
+            // safe than sorry.
+            runningJob = mRunningJob;
+        }
+        if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
             mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
             return;
         }
         this.service = IJobService.Stub.asInterface(service);
         final PowerManager pm =
                 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag());
-        mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, runningJob.getTag());
+        mWakeLock.setWorkSource(new WorkSource(runningJob.getUid()));
         mWakeLock.setReferenceCounted(false);
         mWakeLock.acquire();
         mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
@@ -281,13 +295,15 @@
      * @return True if the binder calling is coming from the client we expect.
      */
     private boolean verifyCallingUid() {
-        if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
-            if (DEBUG) {
-                Slog.d(TAG, "Stale callback received, ignoring.");
+        synchronized (mLock) {
+            if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Stale callback received, ignoring.");
+                }
+                return false;
             }
-            return false;
+            return true;
         }
-        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 11e7605..0004c42 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -24,6 +24,7 @@
 import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.text.format.DateUtils;
 import android.util.AtomicFile;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -552,9 +553,10 @@
                 return null;
             }
 
-            Pair<Long, Long> runtimes;
+            // Tuple of (earliest runtime, latest runtime) in elapsed realtime after disk load.
+            Pair<Long, Long> elapsedRuntimes;
             try {
-                runtimes = buildExecutionTimesFromXml(parser);
+                elapsedRuntimes = buildExecutionTimesFromXml(parser);
             } catch (NumberFormatException e) {
                 if (DEBUG) {
                     Slog.d(TAG, "Error parsing execution time parameters, skipping.");
@@ -562,22 +564,45 @@
                 return null;
             }
 
+            final long elapsedNow = SystemClock.elapsedRealtime();
             if (XML_TAG_PERIODIC.equals(parser.getName())) {
                 try {
                     String val = parser.getAttributeValue(null, "period");
-                    jobBuilder.setPeriodic(Long.valueOf(val));
+                    final long periodMillis = Long.valueOf(val);
+                    jobBuilder.setPeriodic(periodMillis);
+                    // As a sanity check, cap the recreated run time to be no later than 2 periods
+                    // from now. This is the latest the periodic could be pushed out. This could
+                    // happen if the periodic ran early (at the start of its period), and then the
+                    // device rebooted.
+                    if (elapsedRuntimes.second > elapsedNow + 2 * periodMillis) {
+                        final long clampedEarlyRuntimeElapsed = elapsedNow + periodMillis;
+                        final long clampedLateRuntimeElapsed = elapsedNow + 2 * periodMillis;
+                        Slog.w(TAG,
+                                String.format("Periodic job for uid='%d' persisted run-time is" +
+                                                " too big [%s, %s]. Clamping to [%s,%s]",
+                                        uid,
+                                        DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000),
+                                        DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000),
+                                        DateUtils.formatElapsedTime(
+                                                clampedEarlyRuntimeElapsed / 1000),
+                                        DateUtils.formatElapsedTime(
+                                                clampedLateRuntimeElapsed / 1000))
+                        );
+                        elapsedRuntimes =
+                                Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
+                    }
                 } catch (NumberFormatException e) {
                     Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
                     return null;
                 }
             } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
                 try {
-                    if (runtimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
-                        jobBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime());
+                    if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
+                        jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
                     }
-                    if (runtimes.second != JobStatus.NO_LATEST_RUNTIME) {
+                    if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
                         jobBuilder.setOverrideDeadline(
-                                runtimes.second - SystemClock.elapsedRealtime());
+                                elapsedRuntimes.second - elapsedNow);
                     }
                 } catch (NumberFormatException e) {
                     Slog.d(TAG, "Error reading job execution criteria, skipping.");
@@ -598,7 +623,8 @@
             do {
                 eventType = parser.next();
             } while (eventType == XmlPullParser.TEXT);
-            if (!(eventType == XmlPullParser.START_TAG && XML_TAG_EXTRAS.equals(parser.getName()))) {
+            if (!(eventType == XmlPullParser.START_TAG
+                    && XML_TAG_EXTRAS.equals(parser.getName()))) {
                 if (DEBUG) {
                     Slog.d(TAG, "Error reading extras, skipping.");
                 }
@@ -609,7 +635,8 @@
             jobBuilder.setExtras(extras);
             parser.nextTag(); // Consume </extras>
 
-            return new JobStatus(jobBuilder.build(), uid, runtimes.first, runtimes.second);
+            return new JobStatus(
+                    jobBuilder.build(), uid, elapsedRuntimes.first, elapsedRuntimes.second);
         }
 
         private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
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 69c63f3..c02611f 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -82,6 +82,13 @@
         this.numFailures = numFailures;
     }
 
+    /** Copy constructor. */
+    public JobStatus(JobStatus jobStatus) {
+        this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getNumFailures());
+        this.earliestRunTimeElapsedMillis = jobStatus.getEarliestRunTime();
+        this.latestRunTimeElapsedMillis = jobStatus.getLatestRunTimeElapsed();
+    }
+
     /** Create a newly scheduled job. */
     public JobStatus(JobInfo job, int uId) {
         this(job, uId, 0);
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index cda7c32..21c30c7 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 
 import java.io.PrintWriter;
@@ -28,7 +29,7 @@
  * are ready to run, or whether they must be stopped.
  */
 public abstract class StateController {
-    protected static final boolean DEBUG = false;
+    protected static final boolean DEBUG = JobSchedulerService.DEBUG;
     protected Context mContext;
     protected StateChangedListener mStateChangedListener;
     protected boolean mDeviceIdleMode;
diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java
index 88ef366..a4d5bce 100644
--- a/services/core/java/com/android/server/notification/EventConditionProvider.java
+++ b/services/core/java/com/android/server/notification/EventConditionProvider.java
@@ -176,7 +176,7 @@
         }
         mTrackers.clear();
         for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
-            final Context context = user.isOwner() ? mContext : getContextForUser(mContext, user);
+            final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user);
             if (context == null) {
                 Slog.w(TAG, "Unable to create context for user " + user.getIdentifier());
                 continue;
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 8176aff..8abc8fc 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -586,6 +586,8 @@
                     grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
                     grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
                             userId);
+                    grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
+                            userId);
                 }
             }
 
@@ -596,7 +598,10 @@
     private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(
             PackageParser.Package dialerPackage, int userId) {
         if (doesPackageSupportRuntimePermissions(dialerPackage)) {
-            grantRuntimePermissionsLPw(dialerPackage, PHONE_PERMISSIONS, userId);
+            boolean isPhonePermFixed =
+                    mService.hasSystemFeature(PackageManager.FEATURE_WATCH);
+            grantRuntimePermissionsLPw(
+                    dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
             grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);
             grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);
             grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 6c6871f..56c4fb1 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -28,7 +28,6 @@
 import android.util.Slog;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -166,12 +165,7 @@
                     String oatDir = null;
                     if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
                         dexoptType = "dex2oat";
-                        try {
-                            oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
-                        } catch (IOException ioe) {
-                            Slog.w(TAG, "Unable to create oatDir for package: " + pkg.packageName);
-                            return DEX_OPT_FAILED;
-                        }
+                        oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
                     } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
                         dexoptType = "patchoat";
                     } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
@@ -230,8 +224,7 @@
      * cannot be created.
      */
     @Nullable
-    private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
-            throws IOException {
+    private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) {
         if (!pkg.canHaveOatDir()) {
             return null;
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 6e32e5c..b0e43a5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -481,6 +481,7 @@
             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                     "Failed to resolve stage location", e);
         }
+        final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0;
 
         // Verify that stage looks sane with respect to existing application.
         // This currently only ensures packageName, versionCode, and certificate
@@ -488,7 +489,10 @@
         validateInstallLocked();
 
         Preconditions.checkNotNull(mPackageName);
-        Preconditions.checkNotNull(mSignatures);
+        // TODO: fix b/25118622; don't bypass signature check
+        if (!quickInstall) {
+            Preconditions.checkNotNull(mSignatures);
+        }
         Preconditions.checkNotNull(mResolvedBaseFile);
 
         if (!mPermissionsAccepted) {
@@ -598,6 +602,7 @@
      * {@link PackageManagerService}.
      */
     private void validateInstallLocked() throws PackageManagerException {
+        final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0;
         mPackageName = null;
         mVersionCode = -1;
         mSignatures = null;
@@ -621,7 +626,9 @@
 
             final ApkLite apk;
             try {
-                apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
+                // TODO: fix b/25118622; always use PARSE_COLLECT_CERTIFICATES
+                final int parseFlags = quickInstall ? 0 : PackageParser.PARSE_COLLECT_CERTIFICATES;
+                apk = PackageParser.parseApkLite(file, parseFlags);
             } catch (PackageParserException e) {
                 throw PackageManagerException.from(e);
             }
@@ -742,6 +749,7 @@
     }
 
     private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
+        final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0;
         if (!mPackageName.equals(apk.packageName)) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
                     + apk.packageName + " inconsistent with " + mPackageName);
@@ -751,7 +759,8 @@
                     + " version code " + apk.versionCode + " inconsistent with "
                     + mVersionCode);
         }
-        if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
+        // TODO: fix b/25118622; don't bypass signature check
+        if (!quickInstall && !Signature.areExactMatch(mSignatures, apk.signatures)) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     tag + " signatures are inconsistent");
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f7f38db..b3e7f5f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2321,7 +2321,7 @@
                         + mSdkVersion + "; regranting permissions for internal storage");
                 updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, updateFlags);
+            updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
             ver.sdkVersion = mSdkVersion;
 
             // If this is the first boot or an update from pre-M, and it is a normal
@@ -4811,18 +4811,13 @@
             // First try to add the "always" resolution(s) for the current user, if any
             if (alwaysList.size() > 0) {
                 result.addAll(alwaysList);
-            // if there is an "always" for the parent user, add it.
-            } else if (xpDomainInfo != null && xpDomainInfo.bestDomainVerificationStatus
-                    == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
-                result.add(xpDomainInfo.resolveInfo);
             } else {
                 // Add all undefined apps as we want them to appear in the disambiguation dialog.
                 result.addAll(undefinedList);
+                // Maybe add one for the other profile.
                 if (xpDomainInfo != null && (
                         xpDomainInfo.bestDomainVerificationStatus
-                        == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
-                        || xpDomainInfo.bestDomainVerificationStatus
-                        == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK)) {
+                        != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
                     result.add(xpDomainInfo.resolveInfo);
                 }
                 includeBrowser = true;
@@ -7685,8 +7680,8 @@
         // We would never need to extract libs for forward-locked and external packages,
         // since the container service will do it for us. We shouldn't attempt to
         // extract libs from system app when it was not updated.
-        if (pkg.isForwardLocked() || isExternal(pkg) ||
-            (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) ) {
+        if (pkg.isForwardLocked() || pkg.applicationInfo.isExternalAsec() ||
+                (isSystemApp(pkg) && !pkg.isUpdatedSystemApp())) {
             extractLibs = false;
         }
 
@@ -7967,7 +7962,7 @@
         final String codePath = pkg.codePath;
         final File codeFile = new File(codePath);
         final boolean bundledApp = info.isSystemApp() && !info.isUpdatedSystemApp();
-        final boolean asecApp = info.isForwardLocked() || isExternal(info);
+        final boolean asecApp = info.isForwardLocked() || info.isExternalAsec();
 
         info.nativeLibraryRootDir = null;
         info.nativeLibraryRootRequiresIsa = false;
@@ -8365,8 +8360,14 @@
     static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
     static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
 
+    private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
+            int flags) {
+        final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
+        updatePermissionsLPw(changingPkg, pkgInfo, volumeUuid, flags);
+    }
+
     private void updatePermissionsLPw(String changingPkg,
-            PackageParser.Package pkgInfo, int flags) {
+            PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
         // Make sure there are no dangling permission trees.
         Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
         while (it.hasNext()) {
@@ -8435,14 +8436,21 @@
         if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
             for (PackageParser.Package pkg : mPackages.values()) {
                 if (pkg != pkgInfo) {
-                    grantPermissionsLPw(pkg, (flags&UPDATE_PERMISSIONS_REPLACE_ALL) != 0,
-                            changingPkg);
+                    // Only replace for packages on requested volume
+                    final String volumeUuid = getVolumeUuidForPackage(pkg);
+                    final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
+                            && Objects.equals(replaceVolumeUuid, volumeUuid);
+                    grantPermissionsLPw(pkg, replace, changingPkg);
                 }
             }
         }
 
         if (pkgInfo != null) {
-            grantPermissionsLPw(pkgInfo, (flags&UPDATE_PERMISSIONS_REPLACE_PKG) != 0, changingPkg);
+            // Only replace for packages on requested volume
+            final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
+            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
+                    && Objects.equals(replaceVolumeUuid, volumeUuid);
+            grantPermissionsLPw(pkgInfo, replace, changingPkg);
         }
     }
 
@@ -10087,6 +10095,10 @@
         if (!DEFAULT_VERIFY_ENABLE) {
             return false;
         }
+        // TODO: fix b/25118622; don't bypass verification
+        if (Build.IS_DEBUGGABLE && (installFlags & PackageManager.INSTALL_QUICK) != 0) {
+            return false;
+        }
 
         boolean ensureVerifyAppsEnabled = isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS);
 
@@ -12412,6 +12424,7 @@
         final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
         final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)
                 || (args.volumeUuid != null));
+        final boolean quickInstall = ((installFlags & PackageManager.INSTALL_QUICK) != 0);
         boolean replace = false;
         int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
         if (args.move != null) {
@@ -12427,7 +12440,8 @@
         final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                 | PackageParser.PARSE_ENFORCE_CODE
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
-                | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
+                | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
+                | (quickInstall ? PackageParser.PARSE_SKIP_VERIFICATION : 0);
         PackageParser pp = new PackageParser();
         pp.setSeparateProcesses(mSeparateProcesses);
         pp.setDisplayMetrics(mMetrics);
@@ -12457,7 +12471,6 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
         try {
             pp.collectCertificates(pkg, parseFlags);
-            pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
             res.setError("Failed collect during installPackageLI", e);
             return;
@@ -12467,6 +12480,16 @@
 
         /* If the installer passed in a manifest digest, compare it now. */
         if (args.manifestDigest != null) {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectManifestDigest");
+            try {
+                pp.collectManifestDigest(pkg);
+            } catch (PackageParserException e) {
+                res.setError("Failed collect during installPackageLI", e);
+                return;
+            } finally {
+                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            }
+
             if (DEBUG_INSTALL) {
                 final String parsedManifest = pkg.manifestDigest == null ? "null"
                         : pkg.manifestDigest.toString();
@@ -12644,7 +12667,7 @@
             int result = mPackageDexOptimizer
                     .performDexOpt(pkg, null /* instruction sets */, false /* forceDex */,
                             false /* defer */, false /* inclDependencies */,
-                            true /*bootComplete*/, false /*useJit*/);
+                            true /*bootComplete*/, quickInstall /*useJit*/);
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
                 res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath);
@@ -12846,6 +12869,18 @@
         return installFlags;
     }
 
+    private String getVolumeUuidForPackage(PackageParser.Package pkg) {
+        if (isExternal(pkg)) {
+            if (TextUtils.isEmpty(pkg.volumeUuid)) {
+                return StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                return pkg.volumeUuid;
+            }
+        } else {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+    }
+
     private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) {
         if (isExternal(pkg)) {
             if (TextUtils.isEmpty(pkg.volumeUuid)) {
@@ -13862,7 +13897,7 @@
             if (ps != null) {
                 libDirRoot = ps.legacyNativeLibraryPathString;
             }
-            if (p != null && (isExternal(p) || p.isForwardLocked())) {
+            if (p != null && (p.isForwardLocked() || p.applicationInfo.isExternalAsec())) {
                 final long token = Binder.clearCallingIdentity();
                 try {
                     String secureContainerId = cidFromCodePath(p.applicationInfo.getBaseCodePath());
@@ -15716,7 +15751,7 @@
         if (isMounted) {
             if (DEBUG_SD_INSTALL)
                 Log.i(TAG, "Loading packages");
-            loadMediaPackages(processCids, uidArr);
+            loadMediaPackages(processCids, uidArr, externalStorage);
             startCleaningPackages();
             mInstallerService.onSecureContainersAvailable();
         } else {
@@ -15771,7 +15806,8 @@
      * the cid is added to list of removeCids. We currently don't delete stale
      * containers.
      */
-    private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr) {
+    private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr,
+            boolean externalStorage) {
         ArrayList<String> pkgList = new ArrayList<String>();
         Set<AsecInstallArgs> keys = processCids.keySet();
 
@@ -15844,7 +15880,10 @@
             // cases get permissions that the user didn't initially explicitly
             // allow... it would be nice to have some better way to handle
             // this situation.
-            final VersionInfo ver = mSettings.getExternalVersion();
+            final VersionInfo ver = externalStorage ? mSettings.getExternalVersion()
+                    : mSettings.getInternalVersion();
+            final String volumeUuid = externalStorage ? StorageManager.UUID_PRIMARY_PHYSICAL
+                    : StorageManager.UUID_PRIVATE_INTERNAL;
 
             int updateFlags = UPDATE_PERMISSIONS_ALL;
             if (ver.sdkVersion != mSdkVersion) {
@@ -15852,7 +15891,7 @@
                         + mSdkVersion + "; regranting permissions for external");
                 updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, updateFlags);
+            updatePermissionsLPw(null, null, volumeUuid, updateFlags);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
@@ -15986,7 +16025,7 @@
                         + mSdkVersion + "; regranting permissions for " + vol.fsUuid);
                 updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, updateFlags);
+            updatePermissionsLPw(null, null, vol.fsUuid, updateFlags);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 61d2676..8b99305 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -514,7 +514,18 @@
         ArrayList<String> removeStage = new ArrayList<String>();
         for (Map.Entry<String,SharedUserSetting> entry : mSharedUsers.entrySet()) {
             final SharedUserSetting sus = entry.getValue();
-            if (sus == null || sus.packages.size() == 0) {
+            if (sus == null) {
+                removeStage.add(entry.getKey());
+                continue;
+            }
+            // remove packages that are no longer installed
+            for (Iterator<PackageSetting> iter = sus.packages.iterator(); iter.hasNext();) {
+                PackageSetting ps = iter.next();
+                if (mPackages.get(ps.name) == null) {
+                    iter.remove();
+                }
+            }
+            if (sus.packages.size() == 0) {
                 removeStage.add(entry.getKey());
             }
         }
@@ -2264,10 +2275,8 @@
                 //
                 // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS
                 // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES:
-                //   system/core/logd/LogStatistics.cpp
+                //   frameworks/base/libs/packagelistparser
                 //   system/core/run-as/run-as.c
-                //   system/core/sdcard/sdcard.c
-                //   external/libselinux/src/android.c:package_info_init()
                 //
                 sb.setLength(0);
                 sb.append(ai.packageName);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 341410d..b36a22e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -356,6 +356,27 @@
     }
 
     @Override
+    public boolean isSameProfileGroup(int userId, int otherUserId) {
+        if (userId == otherUserId) return true;
+        checkManageUsersPermission("check if in the same profile group");
+        synchronized (mPackagesLock) {
+            return isSameProfileGroupLocked(userId, otherUserId);
+        }
+    }
+
+    private boolean isSameProfileGroupLocked(int userId, int otherUserId) {
+        UserInfo userInfo = getUserInfoLocked(userId);
+        if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+            return false;
+        }
+        UserInfo otherUserInfo = getUserInfoLocked(otherUserId);
+        if (otherUserInfo == null || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+            return false;
+        }
+        return userInfo.profileGroupId == otherUserInfo.profileGroupId;
+    }
+
+    @Override
     public UserInfo getProfileParent(int userHandle) {
         checkManageUsersPermission("get the profile parent");
         synchronized (mPackagesLock) {
@@ -577,8 +598,6 @@
 
     @Override
     public Bundle getUserRestrictions(int userId) {
-        // checkManageUsersPermission("getUserRestrictions");
-
         synchronized (mPackagesLock) {
             Bundle restrictions = mUserRestrictions.get(userId);
             return restrictions != null ? new Bundle(restrictions) : new Bundle();
@@ -1567,7 +1586,7 @@
     public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
         if (UserHandle.getCallingUserId() != userId
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
-            checkManageUsersPermission("Only system can get restrictions for other users/apps");
+            checkManageUsersPermission("get application restrictions for other users/apps");
         }
         synchronized (mPackagesLock) {
             // Read the restrictions from XML
@@ -1578,10 +1597,7 @@
     @Override
     public void setApplicationRestrictions(String packageName, Bundle restrictions,
             int userId) {
-        if (UserHandle.getCallingUserId() != userId
-                || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
-            checkManageUsersPermission("Only system can set restrictions for other users/apps");
-        }
+        checkManageUsersPermission("set application restrictions");
         synchronized (mPackagesLock) {
             if (restrictions == null || restrictions.isEmpty()) {
                 cleanAppRestrictionsForPackage(packageName, userId);
@@ -1602,7 +1618,7 @@
 
     @Override
     public void removeRestrictions() {
-        checkManageUsersPermission("Only system can remove restrictions");
+        checkManageUsersPermission("remove restrictions");
         final int userHandle = UserHandle.getCallingUserId();
         removeRestrictionsForUser(userHandle, true);
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e354029..b38b9ce 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3016,7 +3016,7 @@
 
         // Display task switcher for ALT-TAB.
         if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) {
-            if (mRecentAppsHeldModifiers == 0 && !keyguardOn) {
+            if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
                 final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
                 if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                     mRecentAppsHeldModifiers = shiftlessModifiers;
diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
index db2b941..b06fe58 100644
--- a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -131,7 +131,7 @@
                 }
                 break;
             case MotionEvent.ACTION_HOVER_MOVE:
-                if (event.getDevice().getSources() == InputDevice.SOURCE_MOUSE) {
+                if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                     if (!mMouseHoveringAtEdge && event.getY() == 0) {
                         mCallbacks.onMouseHoverAtTop();
                         mMouseHoveringAtEdge = true;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ced0433..6498dd9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -444,6 +444,9 @@
     // True if we are currently in device idle mode.
     private boolean mDeviceIdleMode;
 
+    // True if we are currently in light device idle mode.
+    private boolean mLightDeviceIdleMode;
+
     // Set of app ids that we will always respect the wake locks for.
     int[] mDeviceIdleWhitelist = new int[0];
 
@@ -2292,12 +2295,18 @@
         }
     }
 
-    private boolean isDeviceIdleModeInternal() {
+    boolean isDeviceIdleModeInternal() {
         synchronized (mLock) {
             return mDeviceIdleMode;
         }
     }
 
+    boolean isLightDeviceIdleModeInternal() {
+        synchronized (mLock) {
+            return mLightDeviceIdleMode;
+        }
+    }
+
     private void handleBatteryStateChangedLocked() {
         mDirty |= DIRTY_BATTERY_STATE;
         updatePowerStateLocked();
@@ -2368,7 +2377,7 @@
         }
     }
 
-    void setDeviceIdleModeInternal(boolean enabled) {
+    boolean setDeviceIdleModeInternal(boolean enabled) {
         synchronized (mLock) {
             if (mDeviceIdleMode != enabled) {
                 mDeviceIdleMode = enabled;
@@ -2378,7 +2387,19 @@
                 } else {
                     EventLogTags.writeDeviceIdleOffPhase("power");
                 }
+                return true;
             }
+            return false;
+        }
+    }
+
+    boolean setLightDeviceIdleModeInternal(boolean enabled) {
+        synchronized (mLock) {
+            if (mLightDeviceIdleMode != enabled) {
+                mLightDeviceIdleMode = enabled;
+                return true;
+            }
+            return false;
         }
     }
 
@@ -2671,6 +2692,7 @@
             pw.println("  mSandmanSummoned=" + mSandmanSummoned);
             pw.println("  mLowPowerModeEnabled=" + mLowPowerModeEnabled);
             pw.println("  mBatteryLevelLow=" + mBatteryLevelLow);
+            pw.println("  mLightDeviceIdleMode=" + mLightDeviceIdleMode);
             pw.println("  mDeviceIdleMode=" + mDeviceIdleMode);
             pw.println("  mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
             pw.println("  mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
@@ -3308,6 +3330,16 @@
             }
         }
 
+        @Override // Binder call
+        public boolean isLightDeviceIdleMode() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return isLightDeviceIdleModeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         /**
          * Reboots the device.
          *
@@ -3576,8 +3608,13 @@
         }
 
         @Override
-        public void setDeviceIdleMode(boolean enabled) {
-            setDeviceIdleModeInternal(enabled);
+        public boolean setDeviceIdleMode(boolean enabled) {
+            return setDeviceIdleModeInternal(enabled);
+        }
+
+        @Override
+        public boolean setLightDeviceIdleMode(boolean enabled) {
+            return setLightDeviceIdleModeInternal(enabled);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index cc51d20..7abc048 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -16,6 +16,30 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManagerInternal.AppTransitionListener;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindTargetAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -49,30 +73,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-import static android.view.WindowManagerInternal.AppTransitionListener;
-import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindTargetAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-
 // State management of app transitions.  When we are preparing for a
 // transition, mNextAppTransition will be the kind of transition to
 // perform or TRANSIT_NONE if we are not waiting.  If we are waiting,
@@ -543,14 +543,15 @@
 
             final int appWidth = appFrame.width();
             final int appHeight = appFrame.height();
+            // mTmpStartRect will contain an area around the launcher icon that was pressed. We will
+            // clip reveal from that area in the final area of the app.
             getDefaultNextAppTransitionStartRect(mTmpStartRect);
 
             float t = 0f;
             if (appHeight > 0) {
                 t = (float) mTmpStartRect.left / appHeight;
             }
-            int translationY = mClipRevealTranslationY
-                    + (int)(appHeight / 7f * t);
+            int translationY = mClipRevealTranslationY + (int)(appHeight / 7f * t);
 
             int centerX = mTmpStartRect.centerX();
             int centerY = mTmpStartRect.centerY();
@@ -562,15 +563,20 @@
                     centerX - halfWidth, centerX + halfWidth, 0, appWidth);
             clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
             clipAnimLR.setDuration((long) (DEFAULT_APP_TRANSITION_DURATION / 2.5f));
+
             Animation clipAnimTB = new ClipRectTBAnimation(centerY - halfHeight - translationY,
                     centerY + halfHeight/ 2 - translationY, 0, appHeight);
             clipAnimTB.setInterpolator(mTouchResponseInterpolator);
             clipAnimTB.setDuration(DEFAULT_APP_TRANSITION_DURATION);
 
-            TranslateYAnimation translateY = new TranslateYAnimation(
-                    Animation.ABSOLUTE, translationY, Animation.ABSOLUTE, 0);
-            translateY.setInterpolator(mLinearOutSlowInInterpolator);
-            translateY.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+            // We might be animating entrance of a docked task, so we need the translate to account
+            // for the app frame in which the window will reside. Every other calculation here
+            // is performed as if the window started at 0,0.
+            translationY -= appFrame.top;
+            TranslateAnimation translate = new TranslateAnimation(-appFrame.left, 0, translationY,
+                    0);
+            translate.setInterpolator(mLinearOutSlowInInterpolator);
+            translate.setDuration(DEFAULT_APP_TRANSITION_DURATION);
 
             // Quick fade-in from icon to app window
             final int alphaDuration = DEFAULT_APP_TRANSITION_DURATION / 4;
@@ -581,7 +587,7 @@
             AnimationSet set = new AnimationSet(false);
             set.addAnimation(clipAnimLR);
             set.addAnimation(clipAnimTB);
-            set.addAnimation(translateY);
+            set.addAnimation(translate);
             set.addAnimation(alpha);
             set.setZAdjustment(Animation.ZORDER_TOP);
             set.initialize(appWidth, appHeight, appWidth, appHeight);
@@ -760,26 +766,24 @@
                     a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
                             containingFrame, surfaceInsets, taskId);
                 } else {
+                    mTmpFromClipRect.set(containingFrame);
+                    // exclude top screen decor (status bar) region from the source clip.
+                    mTmpFromClipRect.top = contentInsets.top;
                     // App window scaling up to become full screen
+                    mTmpToClipRect.set(containingFrame);
                     if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                         // In portrait, we scale the width and clip to the top/left square
                         scale = thumbWidth / appWidth;
                         scaledTopDecor = (int) (scale * contentInsets.top);
                         int unscaledThumbHeight = (int) (thumbHeight / scale);
-                        mTmpFromClipRect.set(containingFrame);
-                        mTmpFromClipRect.bottom = (mTmpFromClipRect.top + unscaledThumbHeight);
-                        mTmpToClipRect.set(containingFrame);
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
                     } else {
                         // In landscape, we scale the height and clip to the top/left square
                         scale = thumbHeight / (appHeight - contentInsets.top);
                         scaledTopDecor = (int) (scale * contentInsets.top);
                         int unscaledThumbWidth = (int) (thumbWidth / scale);
-                        mTmpFromClipRect.set(containingFrame);
-                        mTmpFromClipRect.right = (mTmpFromClipRect.left + unscaledThumbWidth);
-                        mTmpToClipRect.set(containingFrame);
+                        mTmpFromClipRect.right = mTmpFromClipRect.left + unscaledThumbWidth;
                     }
-                    // exclude top screen decor (status bar) region from the source clip.
-                    mTmpFromClipRect.top = contentInsets.top;
 
                     mNextAppTransitionInsets.set(contentInsets);
 
@@ -821,25 +825,23 @@
             }
             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
                 // App window scaling down from full screen
+                mTmpFromClipRect.set(containingFrame);
+                mTmpToClipRect.set(containingFrame);
+                // exclude top screen decor (status bar) region from the destination clip.
+                mTmpToClipRect.top = contentInsets.top;
                 if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                     // In portrait, we scale the width and clip to the top/left square
                     scale = thumbWidth / appWidth;
                     scaledTopDecor = (int) (scale * contentInsets.top);
                     int unscaledThumbHeight = (int) (thumbHeight / scale);
-                    mTmpFromClipRect.set(containingFrame);
-                    mTmpToClipRect.set(containingFrame);
-                    mTmpToClipRect.bottom = (mTmpToClipRect.top + unscaledThumbHeight);
+                    mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
                 } else {
                     // In landscape, we scale the height and clip to the top/left square
                     scale = thumbHeight / (appHeight - contentInsets.top);
                     scaledTopDecor = (int) (scale * contentInsets.top);
                     int unscaledThumbWidth = (int) (thumbWidth / scale);
-                    mTmpFromClipRect.set(containingFrame);
-                    mTmpToClipRect.set(containingFrame);
-                    mTmpToClipRect.right = (mTmpToClipRect.left + unscaledThumbWidth);
+                    mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth;
                 }
-                // exclude top screen decor (status bar) region from the destination clip.
-                mTmpToClipRect.top = contentInsets.top;
 
                 mNextAppTransitionInsets.set(contentInsets);
 
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2828cd0..bf7063f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -367,6 +367,7 @@
         return false;
     }
 
+    // This must be called while inside a transaction.
     boolean showAllWindowsLocked() {
         boolean isAnimating = false;
         final int NW = mAllAppWinAnimators.size();
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 7c2da2d..be3e922 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -56,7 +56,7 @@
             int screenOffset, int maskThickness) {
         mScreenSize = new Point();
         display.getSize(mScreenSize);
-        if (mScreenSize.x != mScreenSize.y) {
+        if (mScreenSize.x != mScreenSize.y + screenOffset) {
             Slog.w(TAG, "Screen dimensions of displayId = " + display.getDisplayId() +
                     "are not equal, circularMask will not be drawn.");
             mDimensionsUnequal = true;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3521682..f5e97e5 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -265,21 +265,27 @@
     }
 
     void broadcastDragEndedLw() {
+        final int myPid = Process.myPid();
+
         if (WindowManagerService.DEBUG_DRAG) {
             Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED");
         }
-        DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
-                0, 0, null, null, null, null, mDragResult);
-        for (WindowState ws: mNotifiedWindows) {
+        for (WindowState ws : mNotifiedWindows) {
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+                    0, 0, null, null, null, null, mDragResult);
             try {
                 ws.mClient.dispatchDragEvent(evt);
             } catch (RemoteException e) {
                 Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws);
             }
+            // if the current window is in the same process,
+            // the dispatch has already recycled the event
+            if (myPid != ws.mSession.mPid) {
+                evt.recycle();
+            }
         }
         mNotifiedWindows.clear();
         mDragInProgress = false;
-        evt.recycle();
     }
 
     void endDragLw() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c4600e0..7c56180 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -326,6 +326,10 @@
         return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
     }
 
+    boolean inDockedWorkspace() {
+        return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
+    }
+
     WindowState getTopAppMainWindow() {
         final int tokensCount = mAppTokens.size();
         return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index b409cea..6734fd6 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -16,10 +16,14 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.*;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
 import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
-import static com.android.server.wm.WindowManagerService.H.UNUSED;
 import static com.android.server.wm.WindowManagerService.TAG;
 
 import android.annotation.IntDef;
@@ -30,8 +34,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.DisplayInfo;
-
 import android.view.Surface;
+
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
@@ -424,12 +428,19 @@
             return;
         }
 
-        final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
+        final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
         if (dockedStack == null) {
             // Not sure why you are calling this method when there is no docked stack...
             throw new IllegalStateException(
                     "Calling getStackDockedModeBoundsLocked() when there is no docked stack.");
         }
+        if (!dockedStack.isVisibleLocked()) {
+            // The docked stack is being dismissed, but we caught before it finished being
+            // dismissed. In that case we want to treat it as if it is not occupying any space and
+            // let others occupy the whole display.
+            mDisplayContent.getLogicalDisplayRect(mTmpRect);
+            return;
+        }
 
         @DockSide
         final int dockedSide = dockedStack.getDockSide();
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index ce1b785..a33fb13 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -16,13 +16,22 @@
 
 package com.android.server.wm;
 
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.DisplayInfo;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicy.PointerEventListener;
 
 import com.android.server.wm.WindowManagerService.H;
 
+import static android.view.PointerIcon.STYLE_NOT_SPECIFIED;
+import static android.view.PointerIcon.STYLE_DEFAULT;
+import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
+
 public class TaskTapPointerEventListener implements PointerEventListener {
     private static final int TAP_TIMEOUT_MSEC = 300;
     private static final float TAP_MOTION_SLOP_INCHES = 0.125f;
@@ -34,6 +43,8 @@
     final private Region mTouchExcludeRegion = new Region();
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
+    private final Rect mTmpRect = new Rect();
+    private int mPointerIconShape = STYLE_NOT_SPECIFIED;
 
     public TaskTapPointerEventListener(WindowManagerService service,
             DisplayContent displayContent) {
@@ -76,6 +87,42 @@
                 break;
             }
 
+            case MotionEvent.ACTION_HOVER_MOVE: {
+                final int x = (int) motionEvent.getX();
+                final int y = (int) motionEvent.getY();
+                final WindowState window = mDisplayContent.findWindowForControlPoint(x, y);
+                if (window == null) {
+                    break;
+                }
+                window.getVisibleBounds(mTmpRect, false);
+                if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
+                    int iconShape = STYLE_DEFAULT;
+                    if (x < mTmpRect.left) {
+                        iconShape =
+                            (y < mTmpRect.top) ? STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
+                            (y > mTmpRect.bottom) ? STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
+                            STYLE_HORIZONTAL_DOUBLE_ARROW;
+                    } else if (x > mTmpRect.right) {
+                        iconShape =
+                            (y < mTmpRect.top) ? STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
+                            (y > mTmpRect.bottom) ? STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
+                            STYLE_HORIZONTAL_DOUBLE_ARROW;
+                    } else if (y < mTmpRect.top || y > mTmpRect.bottom) {
+                        iconShape = STYLE_VERTICAL_DOUBLE_ARROW;
+                    }
+                    if (mPointerIconShape != iconShape) {
+                        mPointerIconShape = iconShape;
+                        motionEvent.getDevice().setPointerShape(iconShape);
+                    }
+                } else {
+                    mPointerIconShape = STYLE_NOT_SPECIFIED;
+                }
+            } break;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+                motionEvent.getDevice().setPointerShape(STYLE_DEFAULT);
+                break;
+
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_POINTER_UP: {
                 int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f571d9c..cd5fbb0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2886,6 +2886,8 @@
                     // isn't affected by the window insets.
                     contentInsets.set(win.mContentInsets);
                     appFrame.set(win.mFrame);
+                } else {
+                    appFrame.set(containingFrame);
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e72d35c..7ea64f1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1326,6 +1326,10 @@
         }
     }
 
+    boolean inDockedWorkspace() {
+        return mAppToken != null && mAppToken.mTask != null && mAppToken.mTask.inDockedWorkspace();
+    }
+
     private class DeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 3c897fa..1754123 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1190,8 +1190,6 @@
             mDtDy = tmpFloats[Matrix.MSCALE_Y];
             float x = tmpFloats[Matrix.MTRANS_X];
             float y = tmpFloats[Matrix.MTRANS_Y];
-            int w = frame.width();
-            int h = frame.height();
             mWin.mShownPosition.set((int) x, (int) y);
 
             // Now set the alpha...  but because our current hardware
@@ -1285,8 +1283,6 @@
             mDtDy = tmpFloats[Matrix.MSCALE_Y];
             float x = tmpFloats[Matrix.MTRANS_X];
             float y = tmpFloats[Matrix.MTRANS_Y];
-            int w = frame.width();
-            int h = frame.height();
             mWin.mShownPosition.set((int) x, (int) y);
 
             mShownAlpha = mAlpha;
@@ -1383,7 +1379,6 @@
             // avoid premature clipping with the system decor rect.
             clipRect.set((mHasClipRect && !fullscreen) ? mClipRect : w.mSystemDecorRect);
         }
-
         // Expand the clip rect for surface insets.
         final WindowManager.LayoutParams attrs = w.mAttrs;
         clipRect.left -= attrs.surfaceInsets.left;
@@ -1397,18 +1392,14 @@
             // clip rect extends outside the system decor rect.
             clipRect.intersect(mClipRect);
         }
-
         // The clip rect was generated assuming (0,0) as the window origin,
         // so we need to translate to match the actual surface coordinates.
         clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
-
         // We don't want to clip to stack bounds windows that are currently doing entrance
-        // animation. This is necessary for docking operation, otherwise the window will be
-        // suddenly cut off.
-        if (!mAnimator.mAnimating) {
+        // animation for docked window, otherwise the animating window will be suddenly cut off.
+        if (!(mAnimator.mAnimating && w.inDockedWorkspace())) {
             adjustCropToStackBounds(w, clipRect);
         }
-
         if (!clipRect.equals(mLastClipRect)) {
             mLastClipRect.set(clipRect);
             try {
@@ -1440,18 +1431,24 @@
         if (appToken != null && appToken.mCropWindowsToStack && !appToken.mReplacingWindow) {
             TaskStack stack = w.getTask().mStack;
             stack.getBounds(mTmpStackBounds);
-            final int surfaceX = (int) mSurfaceX;
-            final int surfaceY = (int) mSurfaceY;
+            // When we resize we use the big surface approach, which means we can't trust the
+            // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
+            // hardcoding it, we use surface coordinates.
+            final boolean isResizing = w.isDragResizing();
+            final int frameX = isResizing ? (int) mSurfaceX :
+                    w.mFrame.left + mWin.mXOffset - w.getAttrs().surfaceInsets.left;
+            final int frameY = isResizing ? (int) mSurfaceY :
+                    w.mFrame.top + mWin.mYOffset - w.getAttrs().surfaceInsets.top;
             // We need to do some acrobatics with surface position, because their clip region is
             // relative to the inside of the surface, but the stack bounds aren't.
             clipRect.left = Math.max(0,
-                    Math.max(mTmpStackBounds.left, surfaceX + clipRect.left) - surfaceX);
+                    Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
             clipRect.top = Math.max(0,
-                    Math.max(mTmpStackBounds.top, surfaceY + clipRect.top) - surfaceY);
+                    Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
             clipRect.right = Math.max(0,
-                    Math.min(mTmpStackBounds.right, surfaceX + clipRect.right) - surfaceX);
+                    Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
             clipRect.bottom = Math.max(0,
-                    Math.min(mTmpStackBounds.bottom, surfaceY + clipRect.bottom) - surfaceY);
+                    Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
         }
     }
 
@@ -1811,8 +1808,6 @@
                     + Debug.getCallers(3));
         }
         if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
-            if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
-                WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null);
             if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW &&
                     mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) {
                 Slog.v(TAG, "Showing " + this
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 247562f..0c004b2 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1147,7 +1147,16 @@
             for (int j = 0; j < windowsCount; j++) {
                 appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
             }
-            mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
+            SurfaceControl.openTransaction();
+            try {
+                mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                        "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
+            }
             mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             int topOpeningLayer = 0;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1d4f047..be190cb 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -199,6 +199,7 @@
     void setShowTouches(bool enabled);
     void setInteractive(bool interactive);
     void reloadCalibration();
+    void setPointerIconShape(int32_t iconId);
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -237,6 +238,8 @@
     /* --- PointerControllerPolicyInterface implementation --- */
 
     virtual void loadPointerResources(PointerResources* outResources);
+    virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources);
+    virtual int32_t getDefaultPointerIconId();
 
 private:
     sp<InputManager> mInputManager;
@@ -779,6 +782,15 @@
             InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION);
 }
 
+void NativeInputManager::setPointerIconShape(int32_t iconId) {
+  AutoMutex _l(mLock);
+  sp<PointerController> controller = mLocked.pointerController.promote();
+  if (controller != NULL) {
+        // Use 0 (the default icon) for ARROW.
+        controller->updatePointerShape(iconId);
+  }
+}
+
 TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
         JNIEnv *env, jfloatArray matrixArr) {
     ScopedFloatArrayRO matrix(env, matrixArr);
@@ -1029,6 +1041,19 @@
             &outResources->spotAnchor);
 }
 
+void NativeInputManager::loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources) {
+    JNIEnv* env = jniEnv();
+
+    for (int iconId = POINTER_ICON_STYLE_CONTEXT_MENU; iconId <= POINTER_ICON_STYLE_GRABBING;
+             ++iconId) {
+        loadSystemIconAsSprite(env, mContextObj, iconId, &((*outResources)[iconId]));
+    }
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_NULL, &((*outResources)[POINTER_ICON_STYLE_NULL]));
+}
+
+int32_t NativeInputManager::getDefaultPointerIconId() {
+    return POINTER_ICON_STYLE_ARROW;
+}
 
 // ----------------------------------------------------------------------------
 
@@ -1367,6 +1392,11 @@
     im->getInputManager()->getDispatcher()->monitor();
 }
 
+static void nativeSetPointerIconShape(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    im->setPointerIconShape(iconId);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -1425,6 +1455,8 @@
             (void*) nativeDump },
     { "nativeMonitor", "(J)V",
             (void*) nativeMonitor },
+    { "nativeSetPointerIconShape", "(JI)V",
+            (void*) nativeSetPointerIconShape },
 };
 
 #define FIND_CLASS(var, className) \
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0860f02..8385685 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3494,7 +3494,7 @@
         final UserHandle caller = mInjector.binderGetCallingUserHandle();
         // If there is a profile owner, redirect to that; otherwise query the device owner.
         ComponentName aliasChooser = getProfileOwner(caller.getIdentifier());
-        if (aliasChooser == null && caller.isOwner()) {
+        if (aliasChooser == null && caller.isSystem()) {
             ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
             if (deviceOwnerAdmin != null) {
                 aliasChooser = deviceOwnerAdmin.info.getComponent();
@@ -3700,7 +3700,10 @@
             return;
         }
         enforceCrossUserPermission(userHandle);
-        enforceNotManagedProfile(userHandle, "set the active password");
+        // Managed Profile password can only be changed when file based encryption is present.
+        if (!"file".equals(SystemProperties.get("ro.crypto.type", "none"))) {
+            enforceNotManagedProfile(userHandle, "set the active password");
+        }
 
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
new file mode 100644
index 0000000..be6861c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -0,0 +1,64 @@
+package com.android.server.content;
+
+import android.os.Bundle;
+
+import junit.framework.TestCase;
+
+public class SyncManagerTest extends TestCase {
+
+    final String KEY_1 = "key_1";
+    final String KEY_2 = "key_2";
+
+    public void testSyncExtrasEquals_WithNull() throws Exception {
+        Bundle b1 = new Bundle();
+        Bundle b2 = new Bundle();
+
+        b1.putString(KEY_1, null);
+        b2.putString(KEY_1, null);
+
+        assertTrue("Null extra not properly compared between bundles.",
+                SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+    }
+
+    public void testSyncExtrasEqualsBigger_WithNull() throws Exception {
+        Bundle b1 = new Bundle();
+        Bundle b2 = new Bundle();
+
+        b1.putString(KEY_1, null);
+        b2.putString(KEY_1, null);
+
+        b1.putString(KEY_2, "bla");
+        b2.putString(KEY_2, "bla");
+
+        assertTrue("Extras not properly compared between bundles.",
+                SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+    }
+
+    public void testSyncExtrasEqualsFails_differentValues() throws Exception {
+        Bundle b1 = new Bundle();
+        Bundle b2 = new Bundle();
+
+        b1.putString(KEY_1, null);
+        b2.putString(KEY_1, null);
+
+        b1.putString(KEY_2, "bla");
+        b2.putString(KEY_2, "ble");  // different key
+
+        assertFalse("Extras considered equal when they are different.",
+                SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+    }
+
+    public void testSyncExtrasEqualsFails_differentNulls() throws Exception {
+        Bundle b1 = new Bundle();
+        Bundle b2 = new Bundle();
+
+        b1.putString(KEY_1, null);
+        b2.putString(KEY_1, "bla");  // different key
+
+        b1.putString(KEY_2, "ble");
+        b2.putString(KEY_2, "ble");
+
+        assertFalse("Extras considered equal when they are different.",
+                SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index bd64392..0b73beb 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,6 +6,7 @@
 import android.app.job.JobInfo;
 import android.app.job.JobInfo.Builder;
 import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.test.AndroidTestCase;
 import android.test.RenamingDelegatingContext;
 import android.util.Log;
@@ -102,6 +103,14 @@
         Iterator<JobStatus> it = jobStatusSet.iterator();
         JobStatus loaded1 = it.next();
         JobStatus loaded2 = it.next();
+
+        // Reverse them so we know which comparison to make.
+        if (loaded1.getJobId() != 8) {
+            JobStatus tmp = loaded1;
+            loaded1 = loaded2;
+            loaded2 = tmp;
+        }
+
         assertTasksEqual(task1, loaded1.getJob());
         assertTasksEqual(task2, loaded2.getJob());
         assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
@@ -143,6 +152,36 @@
         assertTasksEqual(task, loaded.getJob());
     }
 
+    public void testMassivePeriodClampedOnRead() throws Exception {
+        final long TEN_SECONDS = 10000L;
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setPeriodic(TEN_SECONDS)
+                .setPersisted(true);
+        final long invalidLateRuntimeElapsedMillis =
+                SystemClock.elapsedRealtime() + (TEN_SECONDS * 2) + 5000;  // >2P from now.
+        final long invalidEarlyRuntimeElapsedMillis =
+                invalidLateRuntimeElapsedMillis - TEN_SECONDS;  // Early is (late - period).
+        final JobStatus js = new JobStatus(b.build(), SOME_UID,
+                invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis);
+
+        mTaskStoreUnderTest.add(js);
+        Thread.sleep(IO_WAIT);
+
+        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.iterator().next();
+
+        // Assert early runtime was clamped to be under now + period. We can do <= here b/c we'll
+        // call SystemClock.elapsedRealtime after doing the disk i/o.
+        final long newNowElapsed = SystemClock.elapsedRealtime();
+        assertTrue("Early runtime wasn't correctly clamped.",
+                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS);
+        // Assert late runtime was clamped to be now + period*2.
+        assertTrue("Early runtime wasn't correctly clamped.",
+                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS*2);
+    }
+
     /**
      * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
      */
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 5c64168..5ecd2b5 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -52,6 +52,32 @@
 public final class PhoneAccount implements Parcelable {
 
     /**
+     * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
+     * maximum permitted length of a call subject specified via the
+     * {@link TelecomManager#EXTRA_CALL_SUBJECT} extra on an
+     * {@link android.content.Intent#ACTION_CALL} intent.  Ultimately a {@link ConnectionService} is
+     * responsible for enforcing the maximum call subject length when sending the message, however
+     * this extra is provided so that the user interface can proactively limit the length of the
+     * call subject as the user types it.
+     */
+    public static final String EXTRA_CALL_SUBJECT_MAX_LENGTH =
+            "android.telecom.extra.CALL_SUBJECT_MAX_LENGTH";
+
+    /**
+     * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
+     * character encoding to be used when determining the length of messages.
+     * The user interface can use this when determining the number of characters the user may type
+     * in a call subject.  If empty-string, the call subject message size limit will be enforced on
+     * a 1:1 basis.  That is, each character will count towards the messages size limit as a single
+     * character.  If a character encoding is specified, the message size limit will be based on the
+     * number of bytes in the message per the specified encoding.  See
+     * {@link #EXTRA_CALL_SUBJECT_MAX_LENGTH} for more information on the call subject maximum
+     * length.
+     */
+    public static final String EXTRA_CALL_SUBJECT_CHARACTER_ENCODING =
+            "android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING";
+
+    /**
      * Flag indicating that this {@code PhoneAccount} can act as a connection manager for
      * other connections. The {@link ConnectionService} associated with this {@code PhoneAccount}
      * will be allowed to manage phone calls including using its own proprietary phone-call
@@ -122,6 +148,14 @@
     public static final int CAPABILITY_CALL_SUBJECT = 0x40;
 
     /**
+     * Flag indicating that this {@code PhoneAccount} should only be used for emergency calls.
+     * <p>
+     * See {@link #getCapabilities}
+     * @hide
+     */
+    public static final int CAPABILITY_EMERGENCY_CALLS_ONLY = 0x80;
+
+    /**
      * URI scheme for telephone number URIs.
      */
     public static final String SCHEME_TEL = "tel";
@@ -205,6 +239,7 @@
             mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes());
             mIcon = phoneAccount.getIcon();
             mIsEnabled = phoneAccount.isEnabled();
+            mExtras = phoneAccount.getExtras();
         }
 
         /**
@@ -584,9 +619,8 @@
             out.writeInt(1);
             mIcon.writeToParcel(out, flags);
         }
-
-        out.writeBundle(mExtras);
         out.writeByte((byte) (mIsEnabled ? 1 : 0));
+        out.writeBundle(mExtras);
     }
 
     public static final Creator<PhoneAccount> CREATOR
@@ -628,8 +662,8 @@
         } else {
             mIcon = null;
         }
-        mExtras = in.readBundle();
         mIsEnabled = in.readByte() == 1;
+        mExtras = in.readBundle();
     }
 
     @Override
@@ -645,7 +679,7 @@
             sb.append(scheme)
                     .append(" ");
         }
-        sb.append(" Extras : ");
+        sb.append(" Extras: ");
         sb.append(mExtras);
         sb.append("]");
         return sb.toString();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7cf5f73..8ebef32 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -268,6 +268,13 @@
             = "carrier_allow_turnoff_ims_bool";
 
     /**
+     * Flag specifying whether Generic Bootstrapping Architecture capable SIM is required for IMS.
+     * @hide
+     */
+    public static final String KEY_CARRIER_IMS_GBA_REQUIRED_BOOL
+            = "carrier_ims_gba_required_bool";
+
+    /**
      * Flag specifying whether IMS instant lettering is available for the carrier.  {@code True} if
      * instant lettering is available for the carrier, {@code false} otherwise.
      * @hide
@@ -535,6 +542,7 @@
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
+        sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true);
         sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING, "");
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 37ffa06..e11c8d3 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -334,7 +334,8 @@
 
     @Override
     public String toString() {
-        return "{id=" + mId + ", iccId=" + mIccId + " simSlotIndex=" + mSimSlotIndex
+        String iccIdToPrint = mIccId != null ? mIccId.substring(0, 9) + "XXXXXXXXXXX" : null;
+        return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
                 + " displayName=" + mDisplayName + " carrierName=" + mCarrierName
                 + " nameSource=" + mNameSource + " iconTint=" + mIconTint
                 + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d22727d..6b1b6296 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2874,7 +2874,7 @@
     /**
      * Returns all observed cell information from all radios on the
      * device including the primary and neighboring cells. This does
-     * not cause or change the rate of PhoneStateListner#onCellInfoChanged.
+     * not cause or change the rate of PhoneStateListener#onCellInfoChanged.
      *<p>
      * The list can include one or more of {@link android.telephony.CellInfoGsm CellInfoGsm},
      * {@link android.telephony.CellInfoCdma CellInfoCdma},
@@ -2888,6 +2888,9 @@
      * devices this may return null in which case getCellLocation should
      * be called.
      *<p>
+     * This API will return valid data for registered cells on devices with
+     * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}
+     *<p>
      * @return List of CellInfo or null if info unavailable.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index e5c42d5..275476c 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -25,62 +25,71 @@
 
 main := Main.cpp
 sources := \
-	BigBuffer.cpp \
-	BinaryResourceParser.cpp \
-	BindingXmlPullParser.cpp \
+	compile/IdAssigner.cpp \
+	compile/Png.cpp \
+	compile/XmlIdCollector.cpp \
+	flatten/Archive.cpp \
+	flatten/TableFlattener.cpp \
+	flatten/XmlFlattener.cpp \
+	link/AutoVersioner.cpp \
+	link/PrivateAttributeMover.cpp \
+	link/ReferenceLinker.cpp \
+	link/TableMerger.cpp \
+	link/XmlReferenceLinker.cpp \
+	process/SymbolTable.cpp \
+	unflatten/BinaryResourceParser.cpp \
+	unflatten/ResChunkPullParser.cpp \
+	util/BigBuffer.cpp \
+	util/Files.cpp \
+	util/Util.cpp \
 	ConfigDescription.cpp \
 	Debug.cpp \
-	Files.cpp \
-	Flag.cpp \
+	Flags.cpp \
 	JavaClassGenerator.cpp \
-	Linker.cpp \
 	Locale.cpp \
-	Logger.cpp \
-	ManifestMerger.cpp \
-	ManifestParser.cpp \
-	ManifestValidator.cpp \
-	Png.cpp \
 	ProguardRules.cpp \
-	ResChunkPullParser.cpp \
 	Resource.cpp \
 	ResourceParser.cpp \
 	ResourceTable.cpp \
-	ResourceTableResolver.cpp \
+	ResourceUtils.cpp \
 	ResourceValues.cpp \
 	SdkConstants.cpp \
 	StringPool.cpp \
-	TableFlattener.cpp \
-	Util.cpp \
-	ScopedXmlPullParser.cpp \
-	SourceXmlPullParser.cpp \
-	XliffXmlPullParser.cpp \
 	XmlDom.cpp \
-	XmlFlattener.cpp \
-	ZipEntry.cpp \
-	ZipFile.cpp
+	XmlPullParser.cpp
 
 testSources := \
-	BigBuffer_test.cpp \
-	BindingXmlPullParser_test.cpp \
-	Compat_test.cpp \
+	compile/IdAssigner_test.cpp \
+	compile/XmlIdCollector_test.cpp \
+	flatten/FileExportWriter_test.cpp \
+	flatten/TableFlattener_test.cpp \
+	flatten/XmlFlattener_test.cpp \
+	link/AutoVersioner_test.cpp \
+	link/PrivateAttributeMover_test.cpp \
+	link/ReferenceLinker_test.cpp \
+	link/TableMerger_test.cpp \
+	link/XmlReferenceLinker_test.cpp \
+	process/SymbolTable_test.cpp \
+	unflatten/FileExportHeaderReader_test.cpp \
+	util/BigBuffer_test.cpp \
+	util/Maybe_test.cpp \
+	util/StringPiece_test.cpp \
+	util/Util_test.cpp \
 	ConfigDescription_test.cpp \
 	JavaClassGenerator_test.cpp \
-	Linker_test.cpp \
 	Locale_test.cpp \
-	ManifestMerger_test.cpp \
-	ManifestParser_test.cpp \
-	Maybe_test.cpp \
-	NameMangler_test.cpp \
-	ResourceParser_test.cpp \
 	Resource_test.cpp \
+	ResourceParser_test.cpp \
 	ResourceTable_test.cpp \
-	ScopedXmlPullParser_test.cpp \
-	StringPiece_test.cpp \
+	ResourceUtils_test.cpp \
 	StringPool_test.cpp \
-	Util_test.cpp \
-	XliffXmlPullParser_test.cpp \
+	ValueVisitor_test.cpp \
 	XmlDom_test.cpp \
-	XmlFlattener_test.cpp
+	XmlPullParser_test.cpp
+
+toolSources := \
+	compile/Compile.cpp \
+	link/Link.cpp
 
 hostLdLibs :=
 
@@ -101,7 +110,7 @@
 endif
 
 cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
-cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field
+cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions
 
 # ==========================================================
 # Build the host static library: libaapt2
@@ -139,7 +148,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := aapt2
 
-LOCAL_SRC_FILES := $(main)
+LOCAL_SRC_FILES := $(main) $(toolSources)
 
 LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
 LOCAL_LDLIBS += $(hostLdLibs)
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
deleted file mode 100644
index 4f1947a..0000000
--- a/tools/aapt2/BinaryResourceParser.cpp
+++ /dev/null
@@ -1,897 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BinaryResourceParser.h"
-#include "Logger.h"
-#include "ResChunkPullParser.h"
-#include "Resolver.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceTypeExtensions.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <androidfw/TypeWrappers.h>
-#include <map>
-#include <string>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Visitor that converts a reference's resource ID to a resource name,
- * given a mapping from resource ID to resource name.
- */
-struct ReferenceIdToNameVisitor : ValueVisitor {
-    ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver,
-                             std::map<ResourceId, ResourceName>* cache) :
-            mResolver(resolver), mCache(cache) {
-    }
-
-    void visit(Reference& reference, ValueVisitorArgs&) override {
-        idToName(reference);
-    }
-
-    void visit(Attribute& attr, ValueVisitorArgs&) override {
-        for (auto& entry : attr.symbols) {
-            idToName(entry.symbol);
-        }
-    }
-
-    void visit(Style& style, ValueVisitorArgs&) override {
-        if (style.parent.id.isValid()) {
-            idToName(style.parent);
-        }
-
-        for (auto& entry : style.entries) {
-            idToName(entry.key);
-            entry.value->accept(*this, {});
-        }
-    }
-
-    void visit(Styleable& styleable, ValueVisitorArgs&) override {
-        for (auto& attr : styleable.entries) {
-            idToName(attr);
-        }
-    }
-
-    void visit(Array& array, ValueVisitorArgs&) override {
-        for (auto& item : array.items) {
-            item->accept(*this, {});
-        }
-    }
-
-    void visit(Plural& plural, ValueVisitorArgs&) override {
-        for (auto& item : plural.values) {
-            if (item) {
-                item->accept(*this, {});
-            }
-        }
-    }
-
-private:
-    void idToName(Reference& reference) {
-        if (!reference.id.isValid()) {
-            return;
-        }
-
-        auto cacheIter = mCache->find(reference.id);
-        if (cacheIter != mCache->end()) {
-            reference.name = cacheIter->second;
-            reference.id = 0;
-        } else {
-            Maybe<ResourceName> result = mResolver->findName(reference.id);
-            if (result) {
-                reference.name = result.value();
-
-                // Add to cache.
-                mCache->insert({reference.id, reference.name});
-
-                reference.id = 0;
-            }
-        }
-    }
-
-    std::shared_ptr<IResolver> mResolver;
-    std::map<ResourceId, ResourceName>* mCache;
-};
-
-
-BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
-                                           const std::shared_ptr<IResolver>& resolver,
-                                           const Source& source,
-                                           const std::u16string& defaultPackage,
-                                           const void* data,
-                                           size_t len) :
-        mTable(table), mResolver(resolver), mSource(source), mDefaultPackage(defaultPackage),
-        mData(data), mDataLen(len) {
-}
-
-bool BinaryResourceParser::parse() {
-    ResChunkPullParser parser(mData, mDataLen);
-
-    bool error = false;
-    while(ResChunkPullParser::isGoodEvent(parser.next())) {
-        if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
-            Logger::warn(mSource)
-                    << "unknown chunk of type '"
-                    << parser.getChunk()->type
-                    << "'."
-                    << std::endl;
-            continue;
-        }
-
-        error |= !parseTable(parser.getChunk());
-    }
-
-    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
-        Logger::error(mSource)
-                << "bad document: "
-                << parser.getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-    return !error;
-}
-
-bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
-    if (!mSymbolEntries || mSymbolEntryCount == 0) {
-        return false;
-    }
-
-    if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) {
-        return false;
-    }
-
-    // We only support 32 bit offsets right now.
-    const uintptr_t offset = reinterpret_cast<uintptr_t>(data) -
-            reinterpret_cast<uintptr_t>(mData);
-    if (offset > std::numeric_limits<uint32_t>::max()) {
-        return false;
-    }
-
-    for (size_t i = 0; i < mSymbolEntryCount; i++) {
-        if (mSymbolEntries[i].offset == offset) {
-            // This offset is a symbol!
-            const StringPiece16 str = util::getString(mSymbolPool,
-                                                      mSymbolEntries[i].stringIndex);
-            StringPiece16 typeStr;
-            ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr,
-                                                &outSymbol->entry);
-            const ResourceType* type = parseResourceType(typeStr);
-            if (!type) {
-                return false;
-            }
-            if (outSymbol->package.empty()) {
-                outSymbol->package = mTable->getPackage();
-            }
-            outSymbol->type = *type;
-
-            // Since we scan the symbol table in order, we can start looking for the
-            // next symbol from this point.
-            mSymbolEntryCount -= i + 1;
-            mSymbolEntries += i + 1;
-            return true;
-        }
-    }
-    return false;
-}
-
-bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
-    const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk);
-    if (!symbolTableHeader) {
-        Logger::error(mSource)
-                << "could not parse chunk as SymbolTable_header."
-                << std::endl;
-        return false;
-    }
-
-    const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry);
-    if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) {
-        Logger::error(mSource)
-                << "entries extend beyond chunk."
-                << std::endl;
-        return false;
-    }
-
-    mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>(
-            getChunkData(symbolTableHeader->header));
-    mSymbolEntryCount = symbolTableHeader->count;
-
-    ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes,
-                              getChunkDataLen(symbolTableHeader->header) - entrySizeBytes);
-    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
-        Logger::error(mSource)
-                << "failed to parse chunk: "
-                << parser.getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-
-    if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) {
-        Logger::error(mSource)
-                << "expected Symbol string pool."
-                << std::endl;
-        return false;
-    }
-
-    if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) {
-        Logger::error(mSource)
-                << "failed to parse symbol string pool with code: "
-                << mSymbolPool.getError()
-                << "."
-                << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
-    const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
-    if (!tableHeader) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_header."
-                << std::endl;
-        return false;
-    }
-
-    ResChunkPullParser parser(getChunkData(tableHeader->header),
-                              getChunkDataLen(tableHeader->header));
-    while (ResChunkPullParser::isGoodEvent(parser.next())) {
-        switch (parser.getChunk()->type) {
-        case android::RES_STRING_POOL_TYPE:
-            if (mValuePool.getError() == NO_INIT) {
-                if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
-                        NO_ERROR) {
-                    Logger::error(mSource)
-                            << "failed to parse value string pool with code: "
-                            << mValuePool.getError()
-                            << "."
-                            << std::endl;
-                    return false;
-                }
-
-                // Reserve some space for the strings we are going to add.
-                mTable->getValueStringPool().hintWillAdd(
-                        mValuePool.size(), mValuePool.styleCount());
-            } else {
-                Logger::warn(mSource)
-                    << "unexpected string pool."
-                    << std::endl;
-            }
-            break;
-
-        case RES_TABLE_SYMBOL_TABLE_TYPE:
-            if (!parseSymbolTable(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        case RES_TABLE_SOURCE_POOL_TYPE: {
-            if (mSourcePool.setTo(getChunkData(*parser.getChunk()),
-                        getChunkDataLen(*parser.getChunk())) != NO_ERROR) {
-                Logger::error(mSource)
-                        << "failed to parse source pool with code: "
-                        << mSourcePool.getError()
-                        << "."
-                        << std::endl;
-                return false;
-            }
-            break;
-        }
-
-        case android::RES_TABLE_PACKAGE_TYPE:
-            if (!parsePackage(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        default:
-            Logger::warn(mSource)
-                << "unexpected chunk of type "
-                << parser.getChunk()->type
-                << "."
-                << std::endl;
-            break;
-        }
-    }
-
-    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
-        Logger::error(mSource)
-            << "bad resource table: " << parser.getLastError()
-            << "."
-            << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
-    if (mValuePool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no value string pool for ResTable."
-                << std::endl;
-        return false;
-    }
-
-    const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
-    if (!packageHeader) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_header."
-                << std::endl;
-        return false;
-    }
-
-    if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) {
-        // This is the first time the table has it's package ID set.
-        mTable->setPackageId(packageHeader->id);
-    } else if (mTable->getPackageId() != packageHeader->id) {
-        Logger::error(mSource)
-                << "ResTable_package has package ID "
-                << std::hex << packageHeader->id << std::dec
-                << " but ResourceTable has package ID "
-                << std::hex << mTable->getPackageId() << std::dec
-                << std::endl;
-        return false;
-    }
-
-    size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name),
-            sizeof(packageHeader->name) / sizeof(packageHeader->name[0]));
-    if (mTable->getPackage().empty() && len == 0) {
-        mTable->setPackage(mDefaultPackage);
-    } else if (len > 0) {
-        StringPiece16 thisPackage(reinterpret_cast<const char16_t*>(packageHeader->name), len);
-        if (mTable->getPackage().empty()) {
-            mTable->setPackage(thisPackage);
-        } else if (thisPackage != mTable->getPackage()) {
-            Logger::error(mSource)
-                    << "incompatible packages: "
-                    << mTable->getPackage()
-                    << " vs. "
-                    << thisPackage
-                    << std::endl;
-            return false;
-        }
-    }
-
-    ResChunkPullParser parser(getChunkData(packageHeader->header),
-                              getChunkDataLen(packageHeader->header));
-    while (ResChunkPullParser::isGoodEvent(parser.next())) {
-        switch (parser.getChunk()->type) {
-        case android::RES_STRING_POOL_TYPE:
-            if (mTypePool.getError() == NO_INIT) {
-                if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
-                        NO_ERROR) {
-                    Logger::error(mSource)
-                            << "failed to parse type string pool with code "
-                            << mTypePool.getError()
-                            << "."
-                            << std::endl;
-                    return false;
-                }
-            } else if (mKeyPool.getError() == NO_INIT) {
-                if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) !=
-                        NO_ERROR) {
-                    Logger::error(mSource)
-                            << "failed to parse key string pool with code "
-                            << mKeyPool.getError()
-                            << "."
-                            << std::endl;
-                    return false;
-                }
-            } else {
-                Logger::warn(mSource)
-                        << "unexpected string pool."
-                        << std::endl;
-            }
-            break;
-
-        case android::RES_TABLE_TYPE_SPEC_TYPE:
-            if (!parseTypeSpec(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        case android::RES_TABLE_TYPE_TYPE:
-            if (!parseType(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        case RES_TABLE_PUBLIC_TYPE:
-            if (!parsePublic(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        default:
-            Logger::warn(mSource)
-                    << "unexpected chunk of type "
-                    << parser.getChunk()->type
-                    << "."
-                    << std::endl;
-            break;
-        }
-    }
-
-    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
-        Logger::error(mSource)
-                << "bad package: "
-                << parser.getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-
-    // Now go through the table and change resource ID references to
-    // symbolic references.
-
-    ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex);
-    for (auto& type : *mTable) {
-        for (auto& entry : type->entries) {
-            for (auto& configValue : entry->values) {
-                configValue.value->accept(visitor, {});
-            }
-        }
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) {
-    const Public_header* header = convertTo<Public_header>(chunk);
-
-    if (header->typeId == 0) {
-        Logger::error(mSource)
-                << "invalid type ID " << header->typeId << std::endl;
-        return false;
-    }
-
-    const ResourceType* parsedType = parseResourceType(util::getString(mTypePool,
-                                                                       header->typeId - 1));
-    if (!parsedType) {
-        Logger::error(mSource)
-                << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl;
-        return false;
-    }
-
-    const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size;
-    const Public_entry* entry = reinterpret_cast<const Public_entry*>(
-            getChunkData(header->header));
-    for (uint32_t i = 0; i < header->count; i++) {
-        if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) {
-            Logger::error(mSource)
-                    << "Public_entry extends beyond chunk."
-                    << std::endl;
-            return false;
-        }
-
-        const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId };
-        const ResourceName name = {
-                mTable->getPackage(),
-                *parsedType,
-                util::getString(mKeyPool, entry->key.index).toString() };
-
-        SourceLine source;
-        if (mSourcePool.getError() == NO_ERROR) {
-            source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index));
-            source.line = entry->sourceLine;
-        }
-
-        if (!mTable->markPublicAllowMangled(name, resId, source)) {
-            return false;
-        }
-
-        // Add this resource name->id mapping to the index so
-        // that we can resolve all ID references to name references.
-        auto cacheIter = mIdIndex.find(resId);
-        if (cacheIter == mIdIndex.end()) {
-            mIdIndex.insert({ resId, name });
-        }
-
-        entry++;
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
-    if (mTypePool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no type string pool available for ResTable_typeSpec."
-                << std::endl;
-        return false;
-    }
-
-    const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
-    if (!typeSpec) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_typeSpec."
-                << std::endl;
-        return false;
-    }
-
-    if (typeSpec->id == 0) {
-        Logger::error(mSource)
-                << "ResTable_typeSpec has invalid id: "
-                << typeSpec->id
-                << "."
-                << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parseType(const ResChunk_header* chunk) {
-    if (mTypePool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no type string pool available for ResTable_typeSpec."
-                << std::endl;
-        return false;
-    }
-
-    if (mKeyPool.getError() != NO_ERROR) {
-        Logger::error(mSource)
-                << "no key string pool available for ResTable_type."
-                << std::endl;
-        return false;
-    }
-
-    const ResTable_type* type = convertTo<ResTable_type>(chunk);
-    if (!type) {
-        Logger::error(mSource)
-                << "could not parse chunk as ResTable_type."
-                << std::endl;
-        return false;
-    }
-
-    if (type->id == 0) {
-        Logger::error(mSource)
-                << "ResTable_type has invalid id: "
-                << type->id
-                << "."
-                << std::endl;
-        return false;
-    }
-
-    const ConfigDescription config(type->config);
-    const StringPiece16 typeName = util::getString(mTypePool, type->id - 1);
-
-    const ResourceType* parsedType = parseResourceType(typeName);
-    if (!parsedType) {
-        Logger::error(mSource)
-                << "invalid type name '"
-                << typeName
-                << "' for type with ID "
-                << uint32_t(type->id)
-                << "." << std::endl;
-        return false;
-    }
-
-    android::TypeVariant tv(type);
-    for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
-        if (!*it) {
-            continue;
-        }
-
-        const ResTable_entry* entry = *it;
-        const ResourceName name = {
-                mTable->getPackage(),
-                *parsedType,
-                util::getString(mKeyPool, entry->key.index).toString()
-        };
-
-        const ResourceId resId = { mTable->getPackageId(), type->id, it.index() };
-
-        std::unique_ptr<Value> resourceValue;
-        const ResTable_entry_source* sourceBlock = nullptr;
-        if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
-            const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-            if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
-                const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry);
-                data += mapEntry->size - sizeof(*sourceBlock);
-                sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
-            }
-
-            // TODO(adamlesinski): Check that the entry count is valid.
-            resourceValue = parseMapEntry(name, config, mapEntry);
-        } else {
-            if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) {
-                const uint8_t* data = reinterpret_cast<const uint8_t*>(entry);
-                data += entry->size - sizeof(*sourceBlock);
-                sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
-            }
-
-            const Res_value* value = reinterpret_cast<const Res_value*>(
-                    reinterpret_cast<const uint8_t*>(entry) + entry->size);
-            resourceValue = parseValue(name, config, value, entry->flags);
-        }
-
-        if (!resourceValue) {
-            // TODO(adamlesinski): For now this is ok, but it really shouldn't be.
-            continue;
-        }
-
-        SourceLine source = mSource.line(0);
-        if (sourceBlock) {
-            size_t len;
-            const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len);
-            if (str) {
-                source.path.assign(str, len);
-            }
-            source.line = sourceBlock->line;
-        }
-
-        if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) {
-            return false;
-        }
-
-        if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-            if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) {
-                return false;
-            }
-        }
-
-        // Add this resource name->id mapping to the index so
-        // that we can resolve all ID references to name references.
-        auto cacheIter = mIdIndex.find(resId);
-        if (cacheIter == mIdIndex.end()) {
-            mIdIndex.insert({ resId, name });
-        }
-    }
-    return true;
-}
-
-std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
-                                                       const ConfigDescription& config,
-                                                       const Res_value* value,
-                                                       uint16_t flags) {
-    if (name.type == ResourceType::kId) {
-        return util::make_unique<Id>();
-    }
-
-    if (value->dataType == Res_value::TYPE_STRING) {
-        StringPiece16 str = util::getString(mValuePool, value->data);
-
-        const ResStringPool_span* spans = mValuePool.styleAt(value->data);
-        if (spans != nullptr) {
-            StyleString styleStr = { str.toString() };
-            while (spans->name.index != ResStringPool_span::END) {
-                styleStr.spans.push_back(Span{
-                        util::getString(mValuePool, spans->name.index).toString(),
-                        spans->firstChar,
-                        spans->lastChar
-                });
-                spans++;
-            }
-            return util::make_unique<StyledString>(
-                    mTable->getValueStringPool().makeRef(
-                            styleStr, StringPool::Context{1, config}));
-        } else {
-            if (name.type != ResourceType::kString &&
-                    util::stringStartsWith<char16_t>(str, u"res/")) {
-                // This must be a FileReference.
-                return util::make_unique<FileReference>(mTable->getValueStringPool().makeRef(
-                            str, StringPool::Context{ 0, config }));
-            }
-
-            // There are no styles associated with this string, so treat it as
-            // a simple string.
-            return util::make_unique<String>(
-                    mTable->getValueStringPool().makeRef(
-                            str, StringPool::Context{1, config}));
-        }
-    }
-
-    if (value->dataType == Res_value::TYPE_REFERENCE ||
-            value->dataType == Res_value::TYPE_ATTRIBUTE) {
-        const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
-                    Reference::Type::kResource : Reference::Type::kAttribute;
-
-        if (value->data != 0) {
-            // This is a normal reference.
-            return util::make_unique<Reference>(value->data, type);
-        }
-
-        // This reference has an invalid ID. Check if it is an unresolved symbol.
-        ResourceNameRef symbol;
-        if (getSymbol(&value->data, &symbol)) {
-            return util::make_unique<Reference>(symbol, type);
-        }
-
-        // This is not an unresolved symbol, so it must be the magic @null reference.
-        Res_value nullType = {};
-        nullType.dataType = Res_value::TYPE_REFERENCE;
-        return util::make_unique<BinaryPrimitive>(nullType);
-    }
-
-    if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
-        return util::make_unique<RawString>(
-                mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data),
-                                                    StringPool::Context{ 1, config }));
-    }
-
-    // Treat this as a raw binary primitive.
-    return util::make_unique<BinaryPrimitive>(*value);
-}
-
-std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
-                                                           const ConfigDescription& config,
-                                                           const ResTable_map_entry* map) {
-    switch (name.type) {
-        case ResourceType::kStyle:
-            return parseStyle(name, config, map);
-        case ResourceType::kAttr:
-            return parseAttr(name, config, map);
-        case ResourceType::kArray:
-            return parseArray(name, config, map);
-        case ResourceType::kStyleable:
-            return parseStyleable(name, config, map);
-        case ResourceType::kPlurals:
-            return parsePlural(name, config, map);
-        default:
-            break;
-    }
-    return {};
-}
-
-std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
-                                                        const ConfigDescription& config,
-                                                        const ResTable_map_entry* map) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    if (map->parent.ident == 0) {
-        // The parent is either not set or it is an unresolved symbol.
-        // Check to see if it is a symbol.
-        ResourceNameRef symbol;
-        if (getSymbol(&map->parent.ident, &symbol)) {
-            style->parent.name = symbol.toResourceName();
-        }
-    } else {
-         // The parent is a regular reference to a resource.
-        style->parent.id = map->parent.ident;
-    }
-
-    for (const ResTable_map& mapEntry : map) {
-        style->entries.emplace_back();
-        Style::Entry& styleEntry = style->entries.back();
-
-        if (mapEntry.name.ident == 0) {
-            // The map entry's key (attribute) is not set. This must be
-            // a symbol reference, so resolve it.
-            ResourceNameRef symbol;
-            bool result = getSymbol(&mapEntry.name.ident, &symbol);
-            assert(result);
-            styleEntry.key.name = symbol.toResourceName();
-        } else {
-            // The map entry's key (attribute) is a regular reference.
-            styleEntry.key.id = mapEntry.name.ident;
-        }
-
-        // Parse the attribute's value.
-        styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
-        assert(styleEntry.value);
-    }
-    return style;
-}
-
-std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
-                                                           const ConfigDescription& config,
-                                                           const ResTable_map_entry* map) {
-    const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
-    std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
-
-    // First we must discover what type of attribute this is. Find the type mask.
-    auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
-        return entry.name.ident == ResTable_map::ATTR_TYPE;
-    });
-
-    if (typeMaskIter != end(map)) {
-        attr->typeMask = typeMaskIter->value.data;
-    }
-
-    if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
-        for (const ResTable_map& mapEntry : map) {
-            if (Res_INTERNALID(mapEntry.name.ident)) {
-                continue;
-            }
-
-            Attribute::Symbol symbol;
-            symbol.value = mapEntry.value.data;
-            if (mapEntry.name.ident == 0) {
-                // The map entry's key (id) is not set. This must be
-                // a symbol reference, so resolve it.
-                ResourceNameRef symbolName;
-                bool result = getSymbol(&mapEntry.name.ident, &symbolName);
-                assert(result);
-                symbol.symbol.name = symbolName.toResourceName();
-            } else {
-                // The map entry's key (id) is a regular reference.
-                symbol.symbol.id = mapEntry.name.ident;
-            }
-
-            attr->symbols.push_back(std::move(symbol));
-        }
-    }
-
-    // TODO(adamlesinski): Find min, max, i80n, etc attributes.
-    return attr;
-}
-
-std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
-                                                        const ConfigDescription& config,
-                                                        const ResTable_map_entry* map) {
-    std::unique_ptr<Array> array = util::make_unique<Array>();
-    for (const ResTable_map& mapEntry : map) {
-        array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
-    }
-    return array;
-}
-
-std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
-                                                                const ConfigDescription& config,
-                                                                const ResTable_map_entry* map) {
-    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-    for (const ResTable_map& mapEntry : map) {
-        if (mapEntry.name.ident == 0) {
-            // The map entry's key (attribute) is not set. This must be
-            // a symbol reference, so resolve it.
-            ResourceNameRef symbol;
-            bool result = getSymbol(&mapEntry.name.ident, &symbol);
-            assert(result);
-            styleable->entries.emplace_back(symbol);
-        } else {
-            // The map entry's key (attribute) is a regular reference.
-            styleable->entries.emplace_back(mapEntry.name.ident);
-        }
-    }
-    return styleable;
-}
-
-std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
-                                                          const ConfigDescription& config,
-                                                          const ResTable_map_entry* map) {
-    std::unique_ptr<Plural> plural = util::make_unique<Plural>();
-    for (const ResTable_map& mapEntry : map) {
-        std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
-
-        switch (mapEntry.name.ident) {
-            case android::ResTable_map::ATTR_ZERO:
-                plural->values[Plural::Zero] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_ONE:
-                plural->values[Plural::One] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_TWO:
-                plural->values[Plural::Two] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_FEW:
-                plural->values[Plural::Few] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_MANY:
-                plural->values[Plural::Many] = std::move(item);
-                break;
-            case android::ResTable_map::ATTR_OTHER:
-                plural->values[Plural::Other] = std::move(item);
-                break;
-        }
-    }
-    return plural;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp
deleted file mode 100644
index 4b7a656..0000000
--- a/tools/aapt2/BindingXmlPullParser.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BindingXmlPullParser.h"
-#include "Util.h"
-
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding";
-constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
-constexpr const char16_t* kVariableTagName = u"variable";
-constexpr const char* kBindingTagPrefix = "android:binding_";
-
-BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
-        mParser(parser), mOverride(false), mNextTagId(0) {
-}
-
-bool BindingXmlPullParser::readVariableDeclaration() {
-    VarDecl var;
-
-    const auto endAttrIter = mParser->endAttributes();
-    for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
-        if (!attrIter->namespaceUri.empty()) {
-            continue;
-        }
-
-        if (attrIter->name == u"name") {
-            var.name = util::utf16ToUtf8(attrIter->value);
-        } else if (attrIter->name == u"type") {
-            var.type = util::utf16ToUtf8(attrIter->value);
-        }
-    }
-
-    XmlPullParser::skipCurrentElement(mParser.get());
-
-    if (var.name.empty()) {
-        mLastError = "variable declaration missing name";
-        return false;
-    }
-
-    if (var.type.empty()) {
-        mLastError = "variable declaration missing type";
-        return false;
-    }
-
-    mVarDecls.push_back(std::move(var));
-    return true;
-}
-
-bool BindingXmlPullParser::readExpressions() {
-    mOverride = true;
-    std::vector<XmlPullParser::Attribute> expressions;
-    std::string idValue;
-
-    const auto endAttrIter = mParser->endAttributes();
-    for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
-        if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") {
-            idValue = util::utf16ToUtf8(attr->value);
-        } else {
-            StringPiece16 value = util::trimWhitespace(attr->value);
-            if (util::stringStartsWith<char16_t>(value, u"@{") &&
-                    util::stringEndsWith<char16_t>(value, u"}")) {
-                // This is attribute's value is an expression of the form
-                // @{expression}. We need to capture the expression inside.
-                expressions.push_back(XmlPullParser::Attribute{
-                        attr->namespaceUri,
-                        attr->name,
-                        value.substr(2, value.size() - 3).toString()
-                });
-            } else {
-                // This is a normal attribute, use as is.
-                mAttributes.emplace_back(*attr);
-            }
-        }
-    }
-
-    // Check if we have any expressions.
-    if (!expressions.empty()) {
-        // We have expressions, so let's assign the target a tag number
-        // and add it to our targets list.
-        int32_t targetId = mNextTagId++;
-        mTargets.push_back(Target{
-                util::utf16ToUtf8(mParser->getElementName()),
-                idValue,
-                targetId,
-                std::move(expressions)
-        });
-
-        std::stringstream numGen;
-        numGen << kBindingTagPrefix << targetId;
-        mAttributes.push_back(XmlPullParser::Attribute{
-                std::u16string(kAndroidNamespaceUri),
-                std::u16string(u"tag"),
-                util::utf8ToUtf16(numGen.str())
-        });
-    }
-    return true;
-}
-
-XmlPullParser::Event BindingXmlPullParser::next() {
-    // Clear old state in preparation for the next event.
-    mOverride = false;
-    mAttributes.clear();
-
-    while (true) {
-        Event event = mParser->next();
-        if (event == Event::kStartElement) {
-            if (mParser->getElementNamespace().empty() &&
-                    mParser->getElementName() == kVariableTagName) {
-                // This is a variable tag. Record data from it, and
-                // then discard the entire element.
-                if (!readVariableDeclaration()) {
-                    // mLastError is set, so getEvent will return kBadDocument.
-                    return getEvent();
-                }
-                continue;
-            } else {
-                // Check for expressions of the form @{} in attribute text.
-                const auto endAttrIter = mParser->endAttributes();
-                for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
-                    StringPiece16 value = util::trimWhitespace(attr->value);
-                    if (util::stringStartsWith<char16_t>(value, u"@{") &&
-                            util::stringEndsWith<char16_t>(value, u"}")) {
-                        if (!readExpressions()) {
-                            return getEvent();
-                        }
-                        break;
-                    }
-                }
-            }
-        } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
-            if (mParser->getNamespaceUri() == kBindingNamespaceUri) {
-                // Skip binding namespace tags.
-                continue;
-            }
-        }
-        return event;
-    }
-    return Event::kBadDocument;
-}
-
-bool BindingXmlPullParser::writeToFile(std::ostream& out) const {
-    out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-    out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n";
-
-    // Write the variables.
-    out << "  <Variables>\n";
-    for (const VarDecl& v : mVarDecls) {
-        out << "    <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n";
-    }
-    out << "  </Variables>\n";
-
-    // Write the imports.
-
-    std::stringstream tagGen;
-
-    // Write the targets.
-    out << "  <Targets>\n";
-    for (const Target& t : mTargets) {
-        tagGen.str({});
-        tagGen << kBindingTagPrefix << t.tagId;
-        out << "    <Target boundClass=\"" << t.className << "\" id=\"" << t.id
-            << "\" tag=\"" << tagGen.str() << "\">\n";
-        out << "      <Expressions>\n";
-        for (const XmlPullParser::Attribute& a : t.expressions) {
-            out << "        <Expression attribute=\"" << a.namespaceUri << ":" << a.name
-                << "\" text=\"" << a.value << "\"/>\n";
-        }
-        out << "      </Expressions>\n";
-        out << "    </Target>\n";
-    }
-    out << "  </Targets>\n";
-
-    out << "</Layout>\n";
-    return bool(out);
-}
-
-XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const {
-    if (mOverride) {
-        return mAttributes.begin();
-    }
-    return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const {
-    if (mOverride) {
-        return mAttributes.end();
-    }
-    return mParser->endAttributes();
-}
-
-size_t BindingXmlPullParser::getAttributeCount() const {
-    if (mOverride) {
-        return mAttributes.size();
-    }
-    return mParser->getAttributeCount();
-}
-
-XmlPullParser::Event BindingXmlPullParser::getEvent() const {
-    if (!mLastError.empty()) {
-        return Event::kBadDocument;
-    }
-    return mParser->getEvent();
-}
-
-const std::string& BindingXmlPullParser::getLastError() const {
-    if (!mLastError.empty()) {
-        return mLastError;
-    }
-    return mParser->getLastError();
-}
-
-const std::u16string& BindingXmlPullParser::getComment() const {
-    return mParser->getComment();
-}
-
-size_t BindingXmlPullParser::getLineNumber() const {
-    return mParser->getLineNumber();
-}
-
-size_t BindingXmlPullParser::getDepth() const {
-    return mParser->getDepth();
-}
-
-const std::u16string& BindingXmlPullParser::getText() const {
-    return mParser->getText();
-}
-
-const std::u16string& BindingXmlPullParser::getNamespacePrefix() const {
-    return mParser->getNamespacePrefix();
-}
-
-const std::u16string& BindingXmlPullParser::getNamespaceUri() const {
-    return mParser->getNamespaceUri();
-}
-
-bool BindingXmlPullParser::applyPackageAlias(std::u16string* package,
-                                             const std::u16string& defaultPackage) const {
-    return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& BindingXmlPullParser::getElementNamespace() const {
-    return mParser->getElementNamespace();
-}
-
-const std::u16string& BindingXmlPullParser::getElementName() const {
-    return mParser->getElementName();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h
deleted file mode 100644
index cfb16ef..0000000
--- a/tools/aapt2/BindingXmlPullParser.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_BINDING_XML_PULL_PARSER_H
-#define AAPT_BINDING_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <iostream>
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-class BindingXmlPullParser : public XmlPullParser {
-public:
-    BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
-    BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete;
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override;
-    Event next() override;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
-            const override;
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-    bool writeToFile(std::ostream& out) const;
-
-private:
-    struct VarDecl {
-        std::string name;
-        std::string type;
-    };
-
-    struct Import {
-        std::string name;
-        std::string type;
-    };
-
-    struct Target {
-        std::string className;
-        std::string id;
-        int32_t tagId;
-
-        std::vector<XmlPullParser::Attribute> expressions;
-    };
-
-    bool readVariableDeclaration();
-    bool readExpressions();
-
-    std::shared_ptr<XmlPullParser> mParser;
-    std::string mLastError;
-    bool mOverride;
-    std::vector<XmlPullParser::Attribute> mAttributes;
-    std::vector<VarDecl> mVarDecls;
-    std::vector<Target> mTargets;
-    int32_t mNextTagId;
-};
-
-} // namespace aapt
-
-#endif // AAPT_BINDING_XML_PULL_PARSER_H
diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp
deleted file mode 100644
index 28edcb6..0000000
--- a/tools/aapt2/BindingXmlPullParser_test.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SourceXmlPullParser.h"
-#include "BindingXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
-
-TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-          << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-          << "              xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
-          << "              android:id=\"@+id/content\">\n"
-          << "  <variable name=\"user\" type=\"com.android.test.User\"/>\n"
-          << "  <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n"
-          << "            android:layout_height=\"wrap_content\"/>\n"
-          << "</LinearLayout>\n";
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    BindingXmlPullParser parser(sourceParser);
-
-    ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
-    EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"),
-              parser.getNamespaceUri());
-
-    ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next());
-    EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName());
-
-    while (parser.next() == XmlPullParser::Event::kText) {}
-
-    ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
-    EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName());
-
-    ASSERT_EQ(3u, parser.getAttributeCount());
-    const auto endAttr = parser.endAttributes();
-    EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width"));
-    EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height"));
-    EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag"));
-
-    while (parser.next() == XmlPullParser::Event::kText) {}
-
-    ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
-
-    while (parser.next() == XmlPullParser::Event::kText) {}
-
-    ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
-    ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
-}
-
-TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-          << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-          << "              xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
-          << "              android:id=\"@+id/content\">\n"
-          << "  <variable name=\"user\" type=\"com.android.test.User\"/>\n"
-          << "</LinearLayout>\n";
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    BindingXmlPullParser parser(sourceParser);
-
-    while (XmlPullParser::isGoodEvent(parser.next())) {
-        ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent());
-    }
-
-    std::stringstream output;
-    ASSERT_TRUE(parser.writeToFile(output));
-
-    std::string result = output.str();
-    EXPECT_NE(std::string::npos,
-              result.find("<entries name=\"user\" type=\"com.android.test.User\"/>"));
-}
-
-TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-          << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-          << "              xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
-          << "              android:id=\"@+id/content\">\n"
-          << "  <variable name=\"user\"/>\n"
-          << "</LinearLayout>\n";
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    BindingXmlPullParser parser(sourceParser);
-
-    while (XmlPullParser::isGoodEvent(parser.next())) {}
-
-    EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent());
-    EXPECT_FALSE(parser.getLastError().empty());
-}
-
-
-} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 6ddf94a..8120fa7 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -17,8 +17,8 @@
 #include "ConfigDescription.h"
 #include "Locale.h"
 #include "SdkConstants.h"
-#include "StringPiece.h"
-#include "Util.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <string>
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index 67b4b75..4af089d 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -17,7 +17,7 @@
 #ifndef AAPT_CONFIG_DESCRIPTION_H
 #define AAPT_CONFIG_DESCRIPTION_H
 
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <ostream>
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index c57e351..8370816 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "ConfigDescription.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <gtest/gtest.h>
 #include <string>
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index cf222c6..d864f66 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -17,7 +17,8 @@
 #include "Debug.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
 
 #include <algorithm>
 #include <iostream>
@@ -29,102 +30,120 @@
 
 namespace aapt {
 
-struct PrintVisitor : ConstValueVisitor {
-    void visit(const Attribute& attr, ValueVisitorArgs&) override {
+struct PrintVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+
+    void visit(Attribute* attr) override {
         std::cout << "(attr) type=";
-        attr.printMask(std::cout);
+        attr->printMask(&std::cout);
         static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
             android::ResTable_map::TYPE_FLAGS;
-        if (attr.typeMask & kMask) {
-            for (const auto& symbol : attr.symbols) {
-                std::cout << "\n        "
-                          << symbol.symbol.name.entry << " (" << symbol.symbol.id << ") = "
-                          << symbol.value;
+        if (attr->typeMask & kMask) {
+            for (const auto& symbol : attr->symbols) {
+                std::cout << "\n        " << symbol.symbol.name.value().entry;
+                if (symbol.symbol.id) {
+                    std::cout << " (" << symbol.symbol.id.value() << ")";
+                }
+                std::cout << " = " << symbol.value;
             }
         }
     }
 
-    void visit(const Style& style, ValueVisitorArgs&) override {
+    void visit(Style* style) override {
         std::cout << "(style)";
-        if (style.parent.name.isValid() || style.parent.id.isValid()) {
+        if (style->parent) {
             std::cout << " parent=";
-            if (style.parent.name.isValid()) {
-                std::cout << style.parent.name << " ";
+            if (style->parent.value().name) {
+                std::cout << style->parent.value().name.value() << " ";
             }
 
-            if (style.parent.id.isValid()) {
-                std::cout << style.parent.id;
+            if (style->parent.value().id) {
+                std::cout << style->parent.value().id.value();
             }
         }
 
-        for (const auto& entry : style.entries) {
+        for (const auto& entry : style->entries) {
             std::cout << "\n        ";
-            if (entry.key.name.isValid()) {
-                std::cout << entry.key.name.package << ":" << entry.key.name.entry;
+            if (entry.key.name) {
+                std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry;
             }
 
-            if (entry.key.id.isValid()) {
-                std::cout << "(" << entry.key.id << ")";
+            if (entry.key.id) {
+                std::cout << "(" << entry.key.id.value() << ")";
             }
 
             std::cout << "=" << *entry.value;
         }
     }
 
-    void visit(const Array& array, ValueVisitorArgs&) override {
-        array.print(std::cout);
+    void visit(Array* array) override {
+        array->print(&std::cout);
     }
 
-    void visit(const Plural& plural, ValueVisitorArgs&) override {
-        plural.print(std::cout);
+    void visit(Plural* plural) override {
+        plural->print(&std::cout);
     }
 
-    void visit(const Styleable& styleable, ValueVisitorArgs&) override {
-        styleable.print(std::cout);
+    void visit(Styleable* styleable) override {
+        styleable->print(&std::cout);
     }
 
-    void visitItem(const Item& item, ValueVisitorArgs& args) override {
-        item.print(std::cout);
+    void visitItem(Item* item) override {
+        item->print(&std::cout);
     }
 };
 
-void Debug::printTable(const std::shared_ptr<ResourceTable>& table) {
-    std::cout << "Package name=" << table->getPackage();
-    if (table->getPackageId() != ResourceTable::kUnsetPackageId) {
-        std::cout << " id=" << std::hex << table->getPackageId() << std::dec;
-    }
-    std::cout << std::endl;
-
-    for (const auto& type : *table) {
-        std::cout << "  type " << type->type;
-        if (type->typeId != ResourceTableType::kUnsetTypeId) {
-            std::cout << " id=" << std::hex << type->typeId << std::dec;
+void Debug::printTable(ResourceTable* table) {
+    for (auto& package : table->packages) {
+        std::cout << "Package name=" << package->name;
+        if (package->id) {
+            std::cout << " id=" << std::hex << (int) package->id.value() << std::dec;
         }
-        std::cout << " entryCount=" << type->entries.size() << std::endl;
+        std::cout << std::endl;
 
-        std::vector<const ResourceEntry*> sortedEntries;
-        for (const auto& entry : type->entries) {
-            auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
-                    [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
-                        return a->entryId < b->entryId;
-                    });
-            sortedEntries.insert(iter, entry.get());
-        }
-
-        for (const ResourceEntry* entry : sortedEntries) {
-            ResourceId id = { table->getPackageId(), type->typeId, entry->entryId };
-            ResourceName name = { table->getPackage(), type->type, entry->name };
-            std::cout << "    spec resource " << id << " " << name;
-            if (entry->publicStatus.isPublic) {
-                std::cout << " PUBLIC";
+        for (const auto& type : package->types) {
+            std::cout << "  type " << type->type;
+            if (type->id) {
+                std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
             }
-            std::cout << std::endl;
+            std::cout << " entryCount=" << type->entries.size() << std::endl;
 
-            PrintVisitor visitor;
-            for (const auto& value : entry->values) {
-                std::cout << "      (" << value.config << ") ";
-                value.value->accept(visitor, {});
+            std::vector<const ResourceEntry*> sortedEntries;
+            for (const auto& entry : type->entries) {
+                auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
+                        [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
+                            if (a->id && b->id) {
+                                return a->id.value() < b->id.value();
+                            } else if (a->id) {
+                                return true;
+                            } else {
+                                return false;
+                            }
+                        });
+                sortedEntries.insert(iter, entry.get());
+            }
+
+            for (const ResourceEntry* entry : sortedEntries) {
+                ResourceId id(package->id ? package->id.value() : uint8_t(0),
+                              type->id ? type->id.value() : uint8_t(0),
+                              entry->id ? entry->id.value() : uint16_t(0));
+                ResourceName name(package->name, type->type, entry->name);
+
+                std::cout << "    spec resource " << id << " " << name;
+                switch (entry->symbolStatus.state) {
+                case SymbolState::kPublic: std::cout << " PUBLIC"; break;
+                case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break;
+                default: break;
+                }
+
                 std::cout << std::endl;
+
+                PrintVisitor visitor;
+                for (const auto& value : entry->values) {
+                    std::cout << "      (" << value.config << ") ";
+                    value.value->accept(&visitor);
+                    std::cout << std::endl;
+                }
             }
         }
     }
@@ -136,8 +155,7 @@
     return std::distance(names.begin(), iter);
 }
 
-void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table,
-                            const ResourceName& targetStyle) {
+void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) {
     std::map<ResourceName, std::set<ResourceName>> graph;
 
     std::queue<ResourceName> stylesToVisit;
@@ -150,17 +168,16 @@
             continue;
         }
 
-        const ResourceTableType* type;
-        const ResourceEntry* entry;
-        std::tie(type, entry) = table->findResource(styleName);
-        if (entry) {
+        Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
+        if (result) {
+            ResourceEntry* entry = result.value().entry;
             for (const auto& value : entry->values) {
-                visitFunc<Style>(*value.value, [&](const Style& style) {
-                    if (style.parent.name.isValid()) {
-                        parents.insert(style.parent.name);
-                        stylesToVisit.push(style.parent.name);
+                if (Style* style = valueCast<Style>(value.value.get())) {
+                    if (style->parent && style->parent.value().name) {
+                        parents.insert(style->parent.value().name.value());
+                        stylesToVisit.push(style->parent.value().name.value());
                     }
-                });
+                }
             }
         }
     }
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index cdb3dcb..5b0d7d6 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -20,13 +20,11 @@
 #include "Resource.h"
 #include "ResourceTable.h"
 
-#include <memory>
-
 namespace aapt {
 
 struct Debug {
-    static void printTable(const std::shared_ptr<ResourceTable>& table);
-    static void printStyleGraph(const std::shared_ptr<ResourceTable>& table,
+    static void printTable(ResourceTable* table);
+    static void printStyleGraph(ResourceTable* table,
                                 const ResourceName& targetStyle);
 };
 
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
new file mode 100644
index 0000000..7ea26b3
--- /dev/null
+++ b/tools/aapt2/Diagnostics.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_DIAGNOSTICS_H
+#define AAPT_DIAGNOSTICS_H
+
+#include "Source.h"
+
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+struct DiagMessageActual {
+    Source source;
+    std::string message;
+};
+
+struct DiagMessage {
+private:
+    Source mSource;
+    std::stringstream mMessage;
+
+public:
+    DiagMessage() = default;
+
+    DiagMessage(const StringPiece& src) : mSource(src) {
+    }
+
+    DiagMessage(const Source& src) : mSource(src) {
+    }
+
+    template <typename T> DiagMessage& operator<<(const T& value) {
+        mMessage << value;
+        return *this;
+    }
+
+    DiagMessageActual build() const {
+        return DiagMessageActual{ mSource, mMessage.str() };
+    }
+};
+
+struct IDiagnostics {
+    virtual ~IDiagnostics() = default;
+
+    virtual void error(const DiagMessage& message) = 0;
+    virtual void warn(const DiagMessage& message) = 0;
+    virtual void note(const DiagMessage& message) = 0;
+};
+
+struct StdErrDiagnostics : public IDiagnostics {
+    size_t mNumErrors = 0;
+
+    void emit(const DiagMessage& msg, const char* tag) {
+        DiagMessageActual actual = msg.build();
+        if (!actual.source.path.empty()) {
+            std::cerr << actual.source << ": ";
+        }
+        std::cerr << tag << actual.message << "." << std::endl;
+    }
+
+    void error(const DiagMessage& msg) override {
+        if (mNumErrors < 20) {
+            emit(msg, "error: ");
+        }
+        mNumErrors++;
+    }
+
+    void warn(const DiagMessage& msg) override {
+        emit(msg, "warn: ");
+    }
+
+    void note(const DiagMessage& msg) override {
+        emit(msg, "note: ");
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_DIAGNOSTICS_H */
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
deleted file mode 100644
index 76985da..0000000
--- a/tools/aapt2/Flag.cpp
+++ /dev/null
@@ -1,132 +0,0 @@
-#include "Flag.h"
-#include "StringPiece.h"
-
-#include <functional>
-#include <iomanip>
-#include <iostream>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace flag {
-
-struct Flag {
-    std::string name;
-    std::string description;
-    std::function<bool(const StringPiece&, std::string*)> action;
-    bool required;
-    bool* flagResult;
-    bool flagValueWhenSet;
-    bool parsed;
-};
-
-static std::vector<Flag> sFlags;
-static std::vector<std::string> sArgs;
-
-static std::function<bool(const StringPiece&, std::string*)> wrap(
-        const std::function<void(const StringPiece&)>& action) {
-    return [action](const StringPiece& arg, std::string*) -> bool {
-        action(arg);
-        return true;
-    };
-}
-
-void optionalFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action) {
-    sFlags.push_back(Flag{
-            name.toString(), description.toString(), wrap(action),
-            false, nullptr, false, false });
-}
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action) {
-    sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action),
-            true, nullptr, false, false });
-}
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<bool(const StringPiece&, std::string*)> action) {
-    sFlags.push_back(Flag{ name.toString(), description.toString(), action,
-            true, nullptr, false, false });
-}
-
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
-                    bool* result) {
-    sFlags.push_back(Flag{
-            name.toString(), description.toString(), {},
-            false, result, resultWhenSet, false });
-}
-
-void usageAndDie(const StringPiece& command) {
-    std::cerr << command << " [options]";
-    for (const Flag& flag : sFlags) {
-        if (flag.required) {
-            std::cerr << " " << flag.name << " arg";
-        }
-    }
-    std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
-
-    for (const Flag& flag : sFlags) {
-        std::string command = flag.name;
-        if (!flag.flagResult) {
-            command += " arg ";
-        }
-        std::cerr << "  " << std::setw(30) << std::left << command
-                  << flag.description << std::endl;
-    }
-    exit(1);
-}
-
-void parse(int argc, char** argv, const StringPiece& command) {
-    std::string errorStr;
-    for (int i = 0; i < argc; i++) {
-        const StringPiece arg(argv[i]);
-        if (*arg.data() != '-') {
-            sArgs.push_back(arg.toString());
-            continue;
-        }
-
-        bool match = false;
-        for (Flag& flag : sFlags) {
-            if (arg == flag.name) {
-                match = true;
-                flag.parsed = true;
-                if (flag.flagResult) {
-                    *flag.flagResult = flag.flagValueWhenSet;
-                } else {
-                    i++;
-                    if (i >= argc) {
-                        std::cerr << flag.name << " missing argument." << std::endl
-                                  << std::endl;
-                        usageAndDie(command);
-                    }
-
-                    if (!flag.action(argv[i], &errorStr)) {
-                        std::cerr << errorStr << "." << std::endl << std::endl;
-                        usageAndDie(command);
-                    }
-                }
-                break;
-            }
-        }
-
-        if (!match) {
-            std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
-            usageAndDie(command);
-        }
-    }
-
-    for (const Flag& flag : sFlags) {
-        if (flag.required && !flag.parsed) {
-            std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
-            usageAndDie(command);
-        }
-    }
-}
-
-const std::vector<std::string>& getArgs() {
-    return sArgs;
-}
-
-} // namespace flag
-} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
deleted file mode 100644
index e863742..0000000
--- a/tools/aapt2/Flag.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef AAPT_FLAG_H
-#define AAPT_FLAG_H
-
-#include "StringPiece.h"
-
-#include <functional>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace flag {
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action);
-
-void requiredFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<bool(const StringPiece&, std::string*)> action);
-
-void optionalFlag(const StringPiece& name, const StringPiece& description,
-                  std::function<void(const StringPiece&)> action);
-
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
-                    bool* result);
-
-void usageAndDie(const StringPiece& command);
-
-void parse(int argc, char** argv, const StringPiece& command);
-
-const std::vector<std::string>& getArgs();
-
-} // namespace flag
-} // namespace aapt
-
-#endif // AAPT_FLAG_H
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
new file mode 100644
index 0000000..6ae5af7
--- /dev/null
+++ b/tools/aapt2/Flags.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Flags.h"
+#include "util/StringPiece.h"
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description,
+                         std::string* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        *value = arg.toString();
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false});
+    return *this;
+}
+
+Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description,
+                               std::vector<std::string>* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        value->push_back(arg.toString());
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false });
+    return *this;
+}
+
+Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description,
+                           Maybe<std::string>* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        *value = arg.toString();
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
+    return *this;
+}
+
+Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
+                               std::vector<std::string>* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        value->push_back(arg.toString());
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
+    return *this;
+}
+
+Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description,
+                             bool* value) {
+    auto func = [value](const StringPiece& arg) -> bool {
+        *value = true;
+        return true;
+    };
+
+    mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false });
+    return *this;
+}
+
+void Flags::usage(const StringPiece& command, std::ostream* out) {
+    *out << command << " [options]";
+    for (const Flag& flag : mFlags) {
+        if (flag.required) {
+            *out << " " << flag.name << " arg";
+        }
+    }
+
+    *out << " files...\n\nOptions:\n";
+
+    for (const Flag& flag : mFlags) {
+        std::string argLine = flag.name;
+        if (flag.numArgs > 0) {
+            argLine += " arg";
+        }
+        *out << " " << std::setw(30) << std::left << argLine << flag.description << "\n";
+    }
+    *out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n";
+    out->flush();
+}
+
+bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args,
+                  std::ostream* outError) {
+    for (size_t i = 0; i < args.size(); i++) {
+        StringPiece arg = args[i];
+        if (*(arg.data()) != '-') {
+            mArgs.push_back(arg.toString());
+            continue;
+        }
+
+        if (arg == "-h" || arg == "--help") {
+            usage(command, outError);
+            return false;
+        }
+
+        bool match = false;
+        for (Flag& flag : mFlags) {
+            if (arg == flag.name) {
+                if (flag.numArgs > 0) {
+                    i++;
+                    if (i >= args.size()) {
+                        *outError << flag.name << " missing argument.\n\n";
+                        usage(command, outError);
+                        return false;
+                    }
+                    flag.action(args[i]);
+                } else {
+                    flag.action({});
+                }
+                flag.parsed = true;
+                match = true;
+                break;
+            }
+        }
+
+        if (!match) {
+            *outError << "unknown option '" << arg << "'.\n\n";
+            usage(command, outError);
+            return false;
+        }
+    }
+
+    for (const Flag& flag : mFlags) {
+        if (flag.required && !flag.parsed) {
+            *outError << "missing required flag " << flag.name << "\n\n";
+            usage(command, outError);
+            return false;
+        }
+    }
+    return true;
+}
+
+const std::vector<std::string>& Flags::getArgs() {
+    return mArgs;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
new file mode 100644
index 0000000..ce7a485
--- /dev/null
+++ b/tools/aapt2/Flags.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLAGS_H
+#define AAPT_FLAGS_H
+
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
+#include <functional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+class Flags {
+public:
+    Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
+                        std::string* value);
+    Flags& requiredFlagList(const StringPiece& name, const StringPiece& description,
+                            std::vector<std::string>* value);
+    Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
+                        Maybe<std::string>* value);
+    Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
+                            std::vector<std::string>* value);
+    Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
+                          bool* value);
+
+    void usage(const StringPiece& command, std::ostream* out);
+
+    bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
+               std::ostream* outError);
+
+    const std::vector<std::string>& getArgs();
+
+private:
+    struct Flag {
+        std::string name;
+        std::string description;
+        std::function<bool(const StringPiece& value)> action;
+        bool required;
+        size_t numArgs;
+
+        bool parsed;
+    };
+
+    std::vector<Flag> mFlags;
+    std::vector<std::string> mArgs;
+};
+
+} // namespace aapt
+
+#endif // AAPT_FLAGS_H
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
index e2ffe79..cdf1b6a 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -19,7 +19,7 @@
 #include "Resource.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <algorithm>
 #include <ostream>
@@ -32,21 +32,18 @@
 // The number of attributes to emit per line in a Styleable array.
 constexpr size_t kAttribsPerLine = 4;
 
-JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table,
-                                       Options options) :
+JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) :
         mTable(table), mOptions(options) {
 }
 
-static void generateHeader(std::ostream& out, const StringPiece16& package) {
-    out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
-           " *\n"
-           " * This class was automatically generated by the\n"
-           " * aapt tool from the resource data it found. It\n"
-           " * should not be modified by hand.\n"
-           " */\n\n";
-    out << "package " << package << ";"
-        << std::endl
-        << std::endl;
+static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) {
+    *out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+            " *\n"
+            " * This class was automatically generated by the\n"
+            " * aapt tool from the resource data it found. It\n"
+            " * should not be modified by hand.\n"
+            " */\n\n"
+            "package " << packageNameToGenerate << ";\n\n";
 }
 
 static const std::set<StringPiece16> sJavaIdentifiers = {
@@ -80,42 +77,44 @@
     return output;
 }
 
-struct GenArgs : ValueVisitorArgs {
-    GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) :
-            out(o), package(p), entryName(e) {
+bool JavaClassGenerator::skipSymbol(SymbolState state) {
+    switch (mOptions.types) {
+    case JavaClassGeneratorOptions::SymbolTypes::kAll:
+        return false;
+    case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
+        return state == SymbolState::kUndefined;
+    case JavaClassGeneratorOptions::SymbolTypes::kPublic:
+        return state != SymbolState::kPublic;
     }
+    return true;
+}
 
-    std::ostream* out;
-    const std::u16string* package;
-    std::u16string* entryName;
-};
-
-void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
+void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate,
+                                           const std::u16string& entryName,
+                                           const Styleable* styleable,
+                                           std::ostream* out) {
     const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
-    std::ostream* out = static_cast<GenArgs&>(a).out;
-    const std::u16string* package = static_cast<GenArgs&>(a).package;
-    std::u16string* entryName = static_cast<GenArgs&>(a).entryName;
 
     // This must be sorted by resource ID.
     std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
-    sortedAttributes.reserve(styleable.entries.size());
-    for (const auto& attr : styleable.entries) {
+    sortedAttributes.reserve(styleable->entries.size());
+    for (const auto& attr : styleable->entries) {
         // If we are not encoding final attributes, the styleable entry may have no ID
         // if we are building a static library.
-        assert((!mOptions.useFinal || attr.id.isValid()) && "no ID set for Styleable entry");
-        assert(attr.name.isValid() && "no name set for Styleable entry");
-        sortedAttributes.emplace_back(attr.id, attr.name);
+        assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
+        assert(attr.name && "no name set for Styleable entry");
+        sortedAttributes.emplace_back(attr.id ? attr.id.value() : ResourceId(0), attr.name.value());
     }
     std::sort(sortedAttributes.begin(), sortedAttributes.end());
 
     // First we emit the array containing the IDs of each attribute.
     *out << "        "
-         << "public static final int[] " << transform(*entryName) << " = {";
+         << "public static final int[] " << transform(entryName) << " = {";
 
     const size_t attrCount = sortedAttributes.size();
     for (size_t i = 0; i < attrCount; i++) {
         if (i % kAttribsPerLine == 0) {
-            *out << std::endl << "            ";
+            *out << "\n            ";
         }
 
         *out << sortedAttributes[i].first;
@@ -123,44 +122,50 @@
             *out << ", ";
         }
     }
-    *out << std::endl << "        };" << std::endl;
+    *out << "\n        };\n";
 
     // Now we emit the indices into the array.
     for (size_t i = 0; i < attrCount; i++) {
         *out << "        "
              << "public static" << finalModifier
-             << " int " << transform(*entryName);
+             << " int " << transform(entryName);
 
         // We may reference IDs from other packages, so prefix the entry name with
         // the package.
         const ResourceNameRef& itemName = sortedAttributes[i].second;
-        if (itemName.package != *package) {
+        if (!itemName.package.empty() && packageNameToGenerate != itemName.package) {
             *out << "_" << transform(itemName.package);
         }
-        *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl;
+        *out << "_" << transform(itemName.entry) << " = " << i << ";\n";
     }
 }
 
-bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId,
-                                      const ResourceTableType& type, std::ostream& out) {
+bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
+                                      const ResourceTablePackage* package,
+                                      const ResourceTableType* type,
+                                      std::ostream* out) {
     const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
 
     std::u16string unmangledPackage;
     std::u16string unmangledName;
-    for (const auto& entry : type.entries) {
-        ResourceId id = { packageId, type.typeId, entry->entryId };
+    for (const auto& entry : type->entries) {
+        if (skipSymbol(entry->symbolStatus.state)) {
+            continue;
+        }
+
+        ResourceId id(package->id.value(), type->id.value(), entry->id.value());
         assert(id.isValid());
 
         unmangledName = entry->name;
         if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
             // The entry name was mangled, and we successfully unmangled it.
             // Check that we want to emit this symbol.
-            if (package != unmangledPackage) {
+            if (package->name != unmangledPackage) {
                 // Skip the entry if it doesn't belong to the package we're writing.
                 continue;
             }
         } else {
-            if (package != mTable->getPackage()) {
+            if (packageNameToGenerate != package->name) {
                 // We are processing a mangled package name,
                 // but this is a non-mangled resource.
                 continue;
@@ -168,41 +173,56 @@
         }
 
         if (!isValidSymbol(unmangledName)) {
-            ResourceNameRef resourceName = { package, type.type, unmangledName };
+            ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
             std::stringstream err;
             err << "invalid symbol name '" << resourceName << "'";
             mError = err.str();
             return false;
         }
 
-        if (type.type == ResourceType::kStyleable) {
+        if (type->type == ResourceType::kStyleable) {
             assert(!entry->values.empty());
-            entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName });
+            generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
+                    entry->values.front().value.get()), out);
         } else {
-            out << "        " << "public static" << finalModifier
-                << " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
+            *out << "        " << "public static" << finalModifier
+                 << " int " << transform(unmangledName) << " = " << id << ";\n";
         }
     }
     return true;
 }
 
-bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) {
-    const size_t packageId = mTable->getPackageId();
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
+    return generate(packageNameToGenerate, packageNameToGenerate, out);
+}
 
-    generateHeader(out, package);
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
+                                  const StringPiece16& outPackageName, std::ostream* out) {
+    generateHeader(outPackageName, out);
 
-    out << "public final class R {" << std::endl;
+    *out << "public final class R {\n";
 
-    for (const auto& type : *mTable) {
-        out << "    public static final class " << type->type << " {" << std::endl;
-        if (!generateType(package, packageId, *type, out)) {
-            return false;
+    for (const auto& package : mTable->packages) {
+        for (const auto& type : package->types) {
+            StringPiece16 typeStr;
+            if (type->type == ResourceType::kAttrPrivate) {
+                typeStr = toString(ResourceType::kAttr);
+            } else {
+                typeStr = toString(type->type);
+            }
+            *out << "    public static final class " << typeStr << " {\n";
+            if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
+                return false;
+            }
+            *out << "    }\n";
         }
-        out << "    }" << std::endl;
     }
 
-    out << "}" << std::endl;
+    *out << "}\n";
+    out->flush();
     return true;
 }
 
+
+
 } // namespace aapt
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
index f8b9ee3..e53a765 100644
--- a/tools/aapt2/JavaClassGenerator.h
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -20,28 +20,38 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 
+#include "util/StringPiece.h"
+
 #include <ostream>
 #include <string>
 
 namespace aapt {
 
+struct JavaClassGeneratorOptions {
+    /*
+     * Specifies whether to use the 'final' modifier
+     * on resource entries. Default is true.
+     */
+    bool useFinal = true;
+
+    enum class SymbolTypes {
+        kAll,
+        kPublicPrivate,
+        kPublic,
+    };
+
+    /*
+     *
+     */
+    SymbolTypes types = SymbolTypes::kAll;
+};
+
 /*
  * Generates the R.java file for a resource table.
  */
-class JavaClassGenerator : ConstValueVisitor {
+class JavaClassGenerator {
 public:
-    /*
-     * A set of options for this JavaClassGenerator.
-     */
-    struct Options {
-        /*
-         * Specifies whether to use the 'final' modifier
-         * on resource entries. Default is true.
-         */
-        bool useFinal = true;
-    };
-
-    JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options);
+    JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options);
 
     /*
      * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
@@ -50,21 +60,29 @@
      * We need to generate these symbols in a separate file.
      * Returns true on success.
      */
-    bool generate(const std::u16string& package, std::ostream& out);
+    bool generate(const StringPiece16& packageNameToGenerate, std::ostream* out);
 
-    /*
-     * ConstValueVisitor implementation.
-     */
-    void visit(const Styleable& styleable, ValueVisitorArgs& args);
+    bool generate(const StringPiece16& packageNameToGenerate,
+                  const StringPiece16& outputPackageName,
+                  std::ostream* out);
 
     const std::string& getError() const;
 
 private:
-    bool generateType(const std::u16string& package, size_t packageId,
-                      const ResourceTableType& type, std::ostream& out);
+    bool generateType(const StringPiece16& packageNameToGenerate,
+                      const ResourceTablePackage* package,
+                      const ResourceTableType* type,
+                      std::ostream* out);
 
-    std::shared_ptr<const ResourceTable> mTable;
-    Options mOptions;
+    void generateStyleable(const StringPiece16& packageNameToGenerate,
+                           const std::u16string& entryName,
+                           const Styleable* styleable,
+                           std::ostream* out);
+
+    bool skipSymbol(SymbolState state);
+
+    ResourceTable* mTable;
+    JavaClassGeneratorOptions mOptions;
     std::string mError;
 };
 
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index b385ff4..becf99b 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -15,12 +15,9 @@
  */
 
 #include "JavaClassGenerator.h"
-#include "Linker.h"
-#include "MockResolver.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -28,51 +25,34 @@
 
 namespace aapt {
 
-struct JavaClassGeneratorTest : public ::testing::Test {
-    virtual void SetUp() override {
-        mTable = std::make_shared<ResourceTable>();
-        mTable->setPackage(u"android");
-        mTable->setPackageId(0x01);
-    }
+TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/class", ResourceId(0x01020000))
+            .build();
 
-    bool addResource(const ResourceNameRef& name, ResourceId id) {
-        return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 },
-                                   util::make_unique<Id>());
-    }
-
-    std::shared_ptr<ResourceTable> mTable;
-};
-
-TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
-    ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" },
-                            ResourceId{ 0x01, 0x02, 0x0000 }));
-
-    JavaClassGenerator generator(mTable, {});
+    JavaClassGenerator generator(table.get(), {});
 
     std::stringstream out;
-    EXPECT_FALSE(generator.generate(mTable->getPackage(), out));
+    EXPECT_FALSE(generator.generate(u"android", &out));
 }
 
-TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
-    ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" },
-                            ResourceId{ 0x01, 0x02, 0x0000 }));
+TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/hey-man", ResourceId(0x01020000))
+            .addSimple(u"@android:attr/cool.attr", ResourceId(0x01010000))
+            .addValue(u"@android:styleable/hey.dude", ResourceId(0x01030000),
+                      test::StyleableBuilder()
+                              .addItem(u"@android:attr/cool.attr", ResourceId(0x01010000))
+                              .build())
+            .build();
 
-    ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" },
-                            ResourceId{ 0x01, 0x01, 0x0000 }));
-
-    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-    Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"});
-    ref.id = ResourceId{ 0x01, 0x01, 0x0000 };
-    styleable->entries.emplace_back(ref);
-
-    ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" },
-                                    ResourceId{ 0x01, 0x03, 0x0000 }, {},
-                                    SourceLine{ "test.xml", 21 }, std::move(styleable)));
-
-    JavaClassGenerator generator(mTable, {});
+    JavaClassGenerator generator(table.get(), {});
 
     std::stringstream out;
-    EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+    EXPECT_TRUE(generator.generate(u"android", &out));
+
     std::string output = out.str();
 
     EXPECT_NE(std::string::npos,
@@ -85,14 +65,96 @@
               output.find("public static final int hey_dude_cool_attr = 0;"));
 }
 
+TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/one", ResourceId(0x01020000))
+            .addSimple(u"@android:id/com.foo$two", ResourceId(0x01020001))
+            .build();
 
-TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
+    JavaClassGenerator generator(table.get(), {});
+    std::stringstream out;
+    ASSERT_TRUE(generator.generate(u"android", u"com.android.internal", &out));
+
+    std::string output = out.str();
+    EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
+    EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+    EXPECT_EQ(std::string::npos, output.find("two"));
+    EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
+}
+
+TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:^attr-private/one", ResourceId(0x01010000))
+            .build();
+
+    JavaClassGenerator generator(table.get(), {});
+    std::stringstream out;
+    ASSERT_TRUE(generator.generate(u"android", &out));
+
+    std::string output = out.str();
+    EXPECT_NE(std::string::npos, output.find("public static final class attr"));
+    EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private"));
+}
+
+TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
+    StdErrDiagnostics diag;
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/one", ResourceId(0x01020000))
+            .addSimple(u"@android:id/two", ResourceId(0x01020001))
+            .addSimple(u"@android:id/three", ResourceId(0x01020002))
+            .build();
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/one"), {}, {},
+                                      SymbolState::kPublic, &diag));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/two"), {}, {},
+                                      SymbolState::kPrivate, &diag));
+
+    JavaClassGeneratorOptions options;
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_EQ(std::string::npos, output.find("two"));
+        EXPECT_EQ(std::string::npos, output.find("three"));
+    }
+
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+        EXPECT_EQ(std::string::npos, output.find("three"));
+    }
+
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int three = 0x01020002;"));
+    }
+}
+
+/*
+ * TODO(adamlesinski): Re-enable this once we get merging working again.
+ * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
     ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
                             ResourceId{ 0x01, 0x02, 0x0000 }));
     ResourceTable table;
     table.setPackage(u"com.lib");
     ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
-                                  SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
+                                  Source{ "lib.xml", 33 }, util::make_unique<Id>()));
     ASSERT_TRUE(mTable->merge(std::move(table)));
 
     Linker linker(mTable,
@@ -113,34 +175,29 @@
     output = out.str();
     EXPECT_NE(std::string::npos, output.find("int test ="));
     EXPECT_EQ(std::string::npos, output.find("int foo ="));
-}
+}*/
 
-TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
-    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-    styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(),
-                                                     ResourceType::kAttr,
-                                                     u"bar" });
-    styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" });
-    ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {},
-                                    std::move(styleable)));
+TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                .setPackageId(u"android", 0x01)
+                .setPackageId(u"com.lib", 0x02)
+                .addSimple(u"@android:attr/bar", ResourceId(0x01010000))
+                .addSimple(u"@com.lib:attr/bar", ResourceId(0x02010000))
+                .addValue(u"@android:styleable/foo", ResourceId(0x01030000),
+                          test::StyleableBuilder()
+                                  .addItem(u"@android:attr/bar", ResourceId(0x01010000))
+                                  .addItem(u"@com.lib:attr/bar", ResourceId(0x02010000))
+                                  .build())
+                .build();
 
-    std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable,
-            std::map<ResourceName, ResourceId>({
-                    { ResourceName{ u"android", ResourceType::kAttr, u"bar" },
-                      ResourceId{ 0x01, 0x01, 0x0000 } },
-                    { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" },
-                      ResourceId{ 0x02, 0x01, 0x0000 } }}));
-
-    Linker linker(mTable, resolver, {});
-    ASSERT_TRUE(linker.linkAndValidate());
-
-    JavaClassGenerator generator(mTable, {});
+    JavaClassGenerator generator(table.get(), {});
 
     std::stringstream out;
-    EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+    EXPECT_TRUE(generator.generate(u"android", &out));
+
     std::string output = out.str();
-    EXPECT_NE(std::string::npos, output.find("int Foo_bar ="));
-    EXPECT_NE(std::string::npos, output.find("int Foo_com_lib_bar ="));
+    EXPECT_NE(std::string::npos, output.find("int foo_bar ="));
+    EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
deleted file mode 100644
index c37cc93..0000000
--- a/tools/aapt2/Linker.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Linker.h"
-#include "Logger.h"
-#include "NameMangler.h"
-#include "Resolver.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "StringPiece.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <array>
-#include <bitset>
-#include <iostream>
-#include <map>
-#include <ostream>
-#include <set>
-#include <sstream>
-#include <tuple>
-#include <vector>
-
-namespace aapt {
-
-Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) {
-}
-
-Linker::Linker(const std::shared_ptr<ResourceTable>& table,
-               const std::shared_ptr<IResolver>& resolver, const Options& options) :
-        mResolver(resolver), mTable(table), mOptions(options), mError(false) {
-}
-
-bool Linker::linkAndValidate() {
-    std::bitset<256> usedTypeIds;
-    std::array<std::set<uint16_t>, 256> usedIds;
-    usedTypeIds.set(0);
-
-    // Collect which resource IDs are already taken.
-    for (auto& type : *mTable) {
-        if (type->typeId != ResourceTableType::kUnsetTypeId) {
-            // The ID for this type has already been set. We
-            // mark this ID as taken so we don't re-assign it
-            // later.
-            usedTypeIds.set(type->typeId);
-        }
-
-        for (auto& entry : type->entries) {
-            if (type->typeId != ResourceTableType::kUnsetTypeId &&
-                    entry->entryId != ResourceEntry::kUnsetEntryId) {
-                // The ID for this entry has already been set. We
-                // mark this ID as taken so we don't re-assign it
-                // later.
-                usedIds[type->typeId].insert(entry->entryId);
-            }
-        }
-    }
-
-    // Assign resource IDs that are available.
-    size_t nextTypeIndex = 0;
-    for (auto& type : *mTable) {
-        if (type->typeId == ResourceTableType::kUnsetTypeId) {
-            while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) {
-                nextTypeIndex++;
-            }
-            type->typeId = nextTypeIndex++;
-        }
-
-        const auto endEntryIter = std::end(usedIds[type->typeId]);
-        auto nextEntryIter = std::begin(usedIds[type->typeId]);
-        size_t nextIndex = 0;
-        for (auto& entry : type->entries) {
-            if (entry->entryId == ResourceTableType::kUnsetTypeId) {
-                while (nextEntryIter != endEntryIter &&
-                        nextIndex == *nextEntryIter) {
-                    nextIndex++;
-                    ++nextEntryIter;
-                }
-                entry->entryId = nextIndex++;
-            }
-        }
-    }
-
-    // Now do reference linking.
-    for (auto& type : *mTable) {
-        for (auto& entry : type->entries) {
-            if (entry->publicStatus.isPublic && entry->values.empty()) {
-                // A public resource has no values. It will not be encoded
-                // properly without a symbol table. This is a unresolved symbol.
-                addUnresolvedSymbol(ResourceNameRef{
-                        mTable->getPackage(), type->type, entry->name },
-                        entry->publicStatus.source);
-                continue;
-            }
-
-            for (auto& valueConfig : entry->values) {
-                // Dispatch to the right method of this linker
-                // based on the value's type.
-                valueConfig.value->accept(*this, Args{
-                        ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
-                        valueConfig.source
-                });
-            }
-        }
-    }
-    return !mError;
-}
-
-const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
-    return mUnresolvedSymbols;
-}
-
-void Linker::doResolveReference(Reference& reference, const SourceLine& source) {
-    Maybe<ResourceId> result = mResolver->findId(reference.name);
-    if (!result) {
-        addUnresolvedSymbol(reference.name, source);
-        return;
-    }
-    assert(result.value().isValid());
-
-    if (mOptions.linkResourceIds) {
-        reference.id = result.value();
-    } else {
-        reference.id = 0;
-    }
-}
-
-const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) {
-    Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name);
-    if (!result || !result.value().attr) {
-        addUnresolvedSymbol(attribute.name, source);
-        return nullptr;
-    }
-
-    const IResolver::Entry& entry = result.value();
-    assert(entry.id.isValid());
-
-    if (mOptions.linkResourceIds) {
-        attribute.id = entry.id;
-    } else {
-        attribute.id = 0;
-    }
-    return entry.attr;
-}
-
-void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
-    Args& args = static_cast<Args&>(a);
-
-    if (reference.name.entry.empty()) {
-        // We can't have a completely bad reference.
-        if (!reference.id.isValid()) {
-            Logger::error() << "srsly? " << args.referrer << std::endl;
-            assert(reference.id.isValid());
-        }
-
-        // This reference has no name but has an ID.
-        // It is a really bad error to have no name and have the same
-        // package ID.
-        assert(reference.id.packageId() != mTable->getPackageId());
-
-        // The reference goes outside this package, let it stay as a
-        // resource ID because it will not change.
-        return;
-    }
-
-    doResolveReference(reference, args.source);
-
-    // TODO(adamlesinski): Verify the referencedType is another reference
-    // or a compatible primitive.
-}
-
-void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
-        const Attribute& attr, std::unique_ptr<Item>& value) {
-    std::unique_ptr<Item> convertedValue;
-    visitFunc<RawString>(*value, [&](RawString& str) {
-        // This is a raw string, so check if it can be converted to anything.
-        // We can NOT swap value with the converted value in here, since
-        // we called through the original value.
-
-        auto onCreateReference = [&](const ResourceName& name) {
-            // We should never get here. All references would have been
-            // parsed in the parser phase.
-            assert(false);
-        };
-
-        convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr,
-                                                               onCreateReference);
-        if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) {
-            // Last effort is to parse as a string.
-            util::StringBuilder builder;
-            builder.append(*str.value);
-            if (builder) {
-                convertedValue = util::make_unique<String>(
-                        mTable->getValueStringPool().makeRef(builder.str()));
-            }
-        }
-    });
-
-    if (convertedValue) {
-        value = std::move(convertedValue);
-    }
-
-    // Process this new or old value (it can be a reference!).
-    value->accept(*this, Args{ name, source });
-
-    // Flatten the value to see what resource type it is.
-    android::Res_value resValue;
-    value->flatten(resValue);
-
-    // Always allow references.
-    const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE;
-    if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) {
-        Logger::error(source)
-                << *value
-                << " is not compatible with attribute "
-                << attr
-                << "."
-                << std::endl;
-        mError = true;
-    }
-}
-
-void Linker::visit(Style& style, ValueVisitorArgs& a) {
-    Args& args = static_cast<Args&>(a);
-
-    if (style.parent.name.isValid() || style.parent.id.isValid()) {
-        visit(style.parent, a);
-    }
-
-    for (Style::Entry& styleEntry : style.entries) {
-        const Attribute* attr = doResolveAttribute(styleEntry.key, args.source);
-        if (attr) {
-            processAttributeValue(args.referrer, args.source, *attr, styleEntry.value);
-        }
-    }
-}
-
-void Linker::visit(Attribute& attr, ValueVisitorArgs& a) {
-    static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
-            android::ResTable_map::TYPE_FLAGS;
-    if (attr.typeMask & kMask) {
-        for (auto& symbol : attr.symbols) {
-            visit(symbol.symbol, a);
-        }
-    }
-}
-
-void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) {
-    for (auto& attrRef : styleable.entries) {
-        visit(attrRef, a);
-    }
-}
-
-void Linker::visit(Array& array, ValueVisitorArgs& a) {
-    Args& args = static_cast<Args&>(a);
-
-    for (auto& item : array.items) {
-        item->accept(*this, Args{ args.referrer, args.source });
-    }
-}
-
-void Linker::visit(Plural& plural, ValueVisitorArgs& a) {
-    Args& args = static_cast<Args&>(a);
-
-    for (auto& item : plural.values) {
-        if (item) {
-            item->accept(*this, Args{ args.referrer, args.source });
-        }
-    }
-}
-
-void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) {
-    mUnresolvedSymbols[name.toResourceName()].push_back(source);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h
deleted file mode 100644
index 6f03515..0000000
--- a/tools/aapt2/Linker.h
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_LINKER_H
-#define AAPT_LINKER_H
-
-#include "Resolver.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "StringPiece.h"
-
-#include <androidfw/AssetManager.h>
-#include <map>
-#include <memory>
-#include <ostream>
-#include <set>
-#include <vector>
-
-namespace aapt {
-
-/**
- * The Linker has two jobs. It follows resource references
- * and verifies that their targert exists and that their
- * types are compatible. The Linker will also assign resource
- * IDs and fill in all the dependent references with the newly
- * assigned resource IDs.
- *
- * To do this, the Linker builds a graph of references. This
- * can be useful to do other analysis, like building a
- * dependency graph of source files. The hope is to be able to
- * add functionality that operates on the graph without
- * overcomplicating the Linker.
- *
- * TODO(adamlesinski): Build the graph first then run the separate
- * steps over the graph.
- */
-class Linker : ValueVisitor {
-public:
-    struct Options {
-        /**
-         * Assign resource Ids to references when linking.
-         * When building a static library, set this to false.
-         */
-        bool linkResourceIds = true;
-    };
-
-    /**
-     * Create a Linker for the given resource table with the sources available in
-     * IResolver. IResolver should contain the ResourceTable as a source too.
-     */
-    Linker(const std::shared_ptr<ResourceTable>& table,
-           const std::shared_ptr<IResolver>& resolver, const Options& options);
-
-    Linker(const Linker&) = delete;
-
-    virtual ~Linker() = default;
-
-    /**
-     * Entry point to the linker. Assigns resource IDs, follows references,
-     * and validates types. Returns true if all references to defined values
-     * are type-compatible. Missing resource references are recorded but do
-     * not cause this method to fail.
-     */
-    bool linkAndValidate();
-
-    /**
-     * Returns any references to resources that were not defined in any of the
-     * sources.
-     */
-    using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>;
-    const ResourceNameToSourceMap& getUnresolvedReferences() const;
-
-protected:
-    virtual void doResolveReference(Reference& reference, const SourceLine& source);
-    virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source);
-
-    std::shared_ptr<IResolver> mResolver;
-
-private:
-    struct Args : public ValueVisitorArgs {
-        Args(const ResourceNameRef& r, const SourceLine& s);
-
-        const ResourceNameRef& referrer;
-        const SourceLine& source;
-    };
-
-    //
-    // Overrides of ValueVisitor
-    //
-    void visit(Reference& reference, ValueVisitorArgs& args) override;
-    void visit(Attribute& attribute, ValueVisitorArgs& args) override;
-    void visit(Styleable& styleable, ValueVisitorArgs& args) override;
-    void visit(Style& style, ValueVisitorArgs& args) override;
-    void visit(Array& array, ValueVisitorArgs& args) override;
-    void visit(Plural& plural, ValueVisitorArgs& args) override;
-
-    void processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
-                               const Attribute& attr, std::unique_ptr<Item>& value);
-
-    void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source);
-
-    std::shared_ptr<ResourceTable> mTable;
-    std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
-    Options mOptions;
-    bool mError;
-};
-
-} // namespace aapt
-
-#endif // AAPT_LINKER_H
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
deleted file mode 100644
index d897f98..0000000
--- a/tools/aapt2/Linker_test.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Linker.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <gtest/gtest.h>
-#include <string>
-
-namespace aapt {
-
-struct LinkerTest : public ::testing::Test {
-    virtual void SetUp() override {
-        mTable = std::make_shared<ResourceTable>();
-        mTable->setPackage(u"android");
-        mTable->setPackageId(0x01);
-        mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>(
-                mTable, std::vector<std::shared_ptr<const android::AssetManager>>()),
-                Linker::Options{});
-
-        // Create a few attributes for use in the tests.
-
-        addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" },
-                    util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER));
-
-        addResource(ResourceName{ {}, ResourceType::kAttr, u"string" },
-                    util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING));
-
-        addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>());
-
-        addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>());
-
-        std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>(
-                false, android::ResTable_map::TYPE_FLAGS);
-        flagAttr->symbols.push_back(Attribute::Symbol{
-                ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 });
-        flagAttr->symbols.push_back(Attribute::Symbol{
-                ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 });
-        addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr));
-    }
-
-    /*
-     * Convenience method for adding resources with the default configuration and some
-     * bogus source line.
-     */
-    bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) {
-        return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value));
-    }
-
-    std::shared_ptr<ResourceTable> mTable;
-    std::shared_ptr<Linker> mLinker;
-};
-
-TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" },
-                util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123"))));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-}
-
-TEST_F(LinkerTest, EscapeAndConvertRawString) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
-            util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"  123"))
-    });
-    const Style* result = style.get();
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
-    EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get()));
-}
-
-TEST_F(LinkerTest, FailToConvertRawString) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
-            util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?"))
-    });
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_FALSE(mLinker->linkAndValidate());
-}
-
-TEST_F(LinkerTest, ConvertRawStringToString) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
-            util::make_unique<RawString>(
-                    mTable->getValueStringPool().makeRef(u"  \"this  is  \\u00fa\"."))
-    });
-    const Style* result = style.get();
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
-    const String* str = dynamic_cast<const String*>(result->entries.front().value.get());
-    ASSERT_NE(nullptr, str);
-    EXPECT_EQ(*str->value, u"this  is  \u00fa.");
-}
-
-TEST_F(LinkerTest, ConvertRawStringToFlags) {
-    std::unique_ptr<Style> style = util::make_unique<Style>();
-    style->entries.push_back(Style::Entry{
-            ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
-            util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
-    });
-    const Style* result = style.get();
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
-                std::move(style)));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-
-    const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>(
-            result->entries.front().value.get());
-    ASSERT_NE(nullptr, bin);
-    EXPECT_EQ(bin->value.data, 1u | 2u);
-}
-
-TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) {
-    ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" },
-                util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 })));
-
-    ASSERT_TRUE(mLinker->linkAndValidate());
-    EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index eed0ea7..20a2d0c 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "Locale.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <ctype.h>
diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp
index 4e154d6..758e1e3 100644
--- a/tools/aapt2/Locale_test.cpp
+++ b/tools/aapt2/Locale_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "Locale.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <gtest/gtest.h>
 #include <string>
diff --git a/tools/aapt2/Logger.cpp b/tools/aapt2/Logger.cpp
deleted file mode 100644
index 3847185..0000000
--- a/tools/aapt2/Logger.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "Logger.h"
-#include "Source.h"
-
-#include <memory>
-#include <iostream>
-
-namespace aapt {
-
-Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) {
-}
-
-std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr));
-
-void Logger::setLog(const std::shared_ptr<Log>& log) {
-    sLog = log;
-}
-
-std::ostream& Logger::error() {
-    return sLog->err << "error: ";
-}
-
-std::ostream& Logger::error(const Source& source) {
-    return sLog->err << source << ": error: ";
-}
-
-std::ostream& Logger::error(const SourceLine& source) {
-    return sLog->err << source << ": error: ";
-}
-
-std::ostream& Logger::warn() {
-    return sLog->err << "warning: ";
-}
-
-std::ostream& Logger::warn(const Source& source) {
-    return sLog->err << source << ": warning: ";
-}
-
-std::ostream& Logger::warn(const SourceLine& source) {
-    return sLog->err << source << ": warning: ";
-}
-
-std::ostream& Logger::note() {
-    return sLog->out << "note: ";
-}
-
-std::ostream& Logger::note(const Source& source) {
-    return sLog->err << source << ": note: ";
-}
-
-std::ostream& Logger::note(const SourceLine& source) {
-    return sLog->err << source << ": note: ";
-}
-
-SourceLogger::SourceLogger(const Source& source)
-: mSource(source) {
-}
-
-std::ostream& SourceLogger::error() {
-    return Logger::error(mSource);
-}
-
-std::ostream& SourceLogger::error(size_t line) {
-    return Logger::error(SourceLine{ mSource.path, line });
-}
-
-std::ostream& SourceLogger::warn() {
-    return Logger::warn(mSource);
-}
-
-std::ostream& SourceLogger::warn(size_t line) {
-    return Logger::warn(SourceLine{ mSource.path, line });
-}
-
-std::ostream& SourceLogger::note() {
-    return Logger::note(mSource);
-}
-
-std::ostream& SourceLogger::note(size_t line) {
-    return Logger::note(SourceLine{ mSource.path, line });
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/Logger.h b/tools/aapt2/Logger.h
deleted file mode 100644
index eed58b8..0000000
--- a/tools/aapt2/Logger.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_LOGGER_H
-#define AAPT_LOGGER_H
-
-#include "Source.h"
-#include "StringPiece.h"
-
-#include <memory>
-#include <ostream>
-#include <string>
-
-namespace aapt {
-
-struct Log {
-    Log(std::ostream& out, std::ostream& err);
-    Log(const Log& rhs) = delete;
-
-    std::ostream& out;
-    std::ostream& err;
-};
-
-class Logger {
-public:
-    static void setLog(const std::shared_ptr<Log>& log);
-
-    static std::ostream& error();
-    static std::ostream& error(const Source& source);
-    static std::ostream& error(const SourceLine& sourceLine);
-
-    static std::ostream& warn();
-    static std::ostream& warn(const Source& source);
-    static std::ostream& warn(const SourceLine& sourceLine);
-
-    static std::ostream& note();
-    static std::ostream& note(const Source& source);
-    static std::ostream& note(const SourceLine& sourceLine);
-
-private:
-    static std::shared_ptr<Log> sLog;
-};
-
-class SourceLogger {
-public:
-    SourceLogger(const Source& source);
-
-    std::ostream& error();
-    std::ostream& error(size_t line);
-
-    std::ostream& warn();
-    std::ostream& warn(size_t line);
-
-    std::ostream& note();
-    std::ostream& note(size_t line);
-
-private:
-    Source mSource;
-};
-
-} // namespace aapt
-
-#endif // AAPT_LOGGER_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 54a7329..248e7ad 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -14,1262 +14,39 @@
  * limitations under the License.
  */
 
-#include "AppInfo.h"
-#include "BigBuffer.h"
-#include "BinaryResourceParser.h"
-#include "BindingXmlPullParser.h"
-#include "Debug.h"
-#include "Files.h"
-#include "Flag.h"
-#include "JavaClassGenerator.h"
-#include "Linker.h"
-#include "ManifestMerger.h"
-#include "ManifestParser.h"
-#include "ManifestValidator.h"
-#include "NameMangler.h"
-#include "Png.h"
-#include "ProguardRules.h"
-#include "ResourceParser.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "SdkConstants.h"
-#include "SourceXmlPullParser.h"
-#include "StringPiece.h"
-#include "TableFlattener.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-#include "ZipFile.h"
+#include "util/StringPiece.h"
 
-#include <algorithm>
-#include <androidfw/AssetManager.h>
-#include <cstdlib>
-#include <dirent.h>
-#include <errno.h>
-#include <fstream>
 #include <iostream>
-#include <sstream>
-#include <sys/stat.h>
-#include <unordered_set>
-#include <utils/Errors.h>
+#include <vector>
 
-constexpr const char* kAaptVersionStr = "2.0-alpha";
+namespace aapt {
 
-using namespace aapt;
+extern int compile(const std::vector<StringPiece>& args);
+extern int link(const std::vector<StringPiece>& args);
 
-/**
- * Used with smart pointers to free malloc'ed memory.
- */
-struct DeleteMalloc {
-    void operator()(void* ptr) {
-        free(ptr);
-    }
-};
-
-struct StaticLibraryData {
-    Source source;
-    std::unique_ptr<ZipFile> apk;
-};
-
-/**
- * Collect files from 'root', filtering out any files that do not
- * match the FileFilter 'filter'.
- */
-bool walkTree(const Source& root, const FileFilter& filter,
-              std::vector<Source>* outEntries) {
-    bool error = false;
-
-    for (const std::string& dirName : listFiles(root.path)) {
-        std::string dir = root.path;
-        appendPath(&dir, dirName);
-
-        FileType ft = getFileType(dir);
-        if (!filter(dirName, ft)) {
-            continue;
-        }
-
-        if (ft != FileType::kDirectory) {
-            continue;
-        }
-
-        for (const std::string& fileName : listFiles(dir)) {
-            std::string file(dir);
-            appendPath(&file, fileName);
-
-            FileType ft = getFileType(file);
-            if (!filter(fileName, ft)) {
-                continue;
-            }
-
-            if (ft != FileType::kRegular) {
-                Logger::error(Source{ file }) << "not a regular file." << std::endl;
-                error = true;
-                continue;
-            }
-            outEntries->push_back(Source{ file });
-        }
-    }
-    return !error;
-}
-
-void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
-    for (auto& type : *table) {
-        if (type->type != ResourceType::kStyle) {
-            continue;
-        }
-
-        for (auto& entry : type->entries) {
-            // Add the versioned styles we want to create
-            // here. They are added to the table after
-            // iterating over the original set of styles.
-            //
-            // A stack is used since auto-generated styles
-            // from later versions should override
-            // auto-generated styles from earlier versions.
-            // Iterating over the styles is done in order,
-            // so we will always visit sdkVersions from smallest
-            // to largest.
-            std::stack<ResourceConfigValue> addStack;
-
-            for (ResourceConfigValue& configValue : entry->values) {
-                visitFunc<Style>(*configValue.value, [&](Style& style) {
-                    // Collect which entries we've stripped and the smallest
-                    // SDK level which was stripped.
-                    size_t minSdkStripped = std::numeric_limits<size_t>::max();
-                    std::vector<Style::Entry> stripped;
-
-                    // Iterate over the style's entries and erase/record the
-                    // attributes whose SDK level exceeds the config's sdkVersion.
-                    auto iter = style.entries.begin();
-                    while (iter != style.entries.end()) {
-                        if (iter->key.name.package == u"android") {
-                            size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
-                            if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
-                                // Record that we are about to strip this.
-                                stripped.emplace_back(std::move(*iter));
-                                minSdkStripped = std::min(minSdkStripped, sdkLevel);
-
-                                // Erase this from this style.
-                                iter = style.entries.erase(iter);
-                                continue;
-                            }
-                        }
-                        ++iter;
-                    }
-
-                    if (!stripped.empty()) {
-                        // We have stripped attributes, so let's create a new style to hold them.
-                        ConfigDescription versionConfig(configValue.config);
-                        versionConfig.sdkVersion = minSdkStripped;
-
-                        ResourceConfigValue value = {
-                                versionConfig,
-                                configValue.source,
-                                {},
-
-                                // Create a copy of the original style.
-                                std::unique_ptr<Value>(configValue.value->clone(
-                                            &table->getValueStringPool()))
-                        };
-
-                        Style& newStyle = static_cast<Style&>(*value.value);
-
-                        // Move the recorded stripped attributes into this new style.
-                        std::move(stripped.begin(), stripped.end(),
-                                  std::back_inserter(newStyle.entries));
-
-                        // We will add this style to the table later. If we do it now, we will
-                        // mess up iteration.
-                        addStack.push(std::move(value));
-                    }
-                });
-            }
-
-            auto comparator =
-                    [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
-                        return lhs.config < rhs;
-                    };
-
-            while (!addStack.empty()) {
-                ResourceConfigValue& value = addStack.top();
-                auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
-                                             value.config, comparator);
-                if (iter == entry->values.end() || iter->config != value.config) {
-                    entry->values.insert(iter, std::move(value));
-                }
-                addStack.pop();
-            }
-        }
-    }
-}
-
-struct CompileItem {
-    ResourceName name;
-    ConfigDescription config;
-    Source source;
-    std::string extension;
-};
-
-struct LinkItem {
-    ResourceName name;
-    ConfigDescription config;
-    Source source;
-    std::string originalPath;
-    ZipFile* apk;
-    std::u16string originalPackage;
-};
-
-template <typename TChar>
-static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
-    auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
-    if (iter == str.end()) {
-        return BasicStringPiece<TChar>();
-    }
-    size_t offset = (iter - str.begin()) + 1;
-    return str.substr(offset, str.size() - offset);
-}
-
-std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                               const StringPiece& extension) {
-    std::stringstream path;
-    path << "res/" << name.type;
-    if (config != ConfigDescription{}) {
-        path << "-" << config;
-    }
-    path << "/" << util::utf16ToUtf8(name.entry);
-    if (!extension.empty()) {
-        path << "." << extension;
-    }
-    return path.str();
-}
-
-std::string buildFileReference(const CompileItem& item) {
-    return buildFileReference(item.name, item.config, item.extension);
-}
-
-std::string buildFileReference(const LinkItem& item) {
-    return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
-}
-
-template <typename T>
-bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
-    StringPool& pool = table->getValueStringPool();
-    StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
-                                       StringPool::Context{ 0, item.config });
-    return table->addResource(item.name, item.config, item.source.line(0),
-                              util::make_unique<FileReference>(ref));
-}
-
-struct AaptOptions {
-    enum class Phase {
-        Link,
-        Compile,
-        Dump,
-        DumpStyleGraph,
-    };
-
-    enum class PackageType {
-        StandardApp,
-        StaticLibrary,
-    };
-
-    // The phase to process.
-    Phase phase;
-
-    // The type of package to produce.
-    PackageType packageType = PackageType::StandardApp;
-
-    // Details about the app.
-    AppInfo appInfo;
-
-    // The location of the manifest file.
-    Source manifest;
-
-    // The APK files to link.
-    std::vector<Source> input;
-
-    // The libraries these files may reference.
-    std::vector<Source> libraries;
-
-    // Output path. This can be a directory or file
-    // depending on the phase.
-    Source output;
-
-    // Directory in which to write binding xml files.
-    Source bindingOutput;
-
-    // Directory to in which to generate R.java.
-    Maybe<Source> generateJavaClass;
-
-    // File in which to produce proguard rules.
-    Maybe<Source> generateProguardRules;
-
-    // Whether to output verbose details about
-    // compilation.
-    bool verbose = false;
-
-    // Whether or not to auto-version styles or layouts
-    // referencing attributes defined in a newer SDK
-    // level than the style or layout is defined for.
-    bool versionStylesAndLayouts = true;
-
-    // The target style that will have it's style hierarchy dumped
-    // when the phase is DumpStyleGraph.
-    ResourceName dumpStyleTarget;
-};
-
-struct IdCollector : public xml::Visitor {
-    IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
-            mSource(source), mTable(table) {
-    }
-
-    virtual void visit(xml::Text* node) override {}
-
-    virtual void visit(xml::Namespace* node) override {
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-    }
-
-    virtual void visit(xml::Element* node) override {
-        for (const xml::Attribute& attr : node->attributes) {
-            bool create = false;
-            bool priv = false;
-            ResourceNameRef nameRef;
-            if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
-                if (create) {
-                    mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
-                                        util::make_unique<Id>());
-                }
-            }
-        }
-
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-    }
-
-private:
-    Source mSource;
-    std::shared_ptr<ResourceTable> mTable;
-};
-
-bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-                const CompileItem& item, ZipFile* outApk) {
-    std::ifstream in(item.source.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(item.source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    SourceLogger logger(item.source);
-    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
-    if (!root) {
-        return false;
-    }
-
-    // Collect any resource ID's declared here.
-    IdCollector idCollector(item.source, table);
-    root->accept(&idCollector);
-
-    BigBuffer outBuffer(1024);
-    if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
-        logger.error() << "failed to encode XML." << std::endl;
-        return false;
-    }
-
-    // Write the resulting compiled XML file to the output APK.
-    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
-                nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write compiled '" << item.source
-                                      << "' to apk." << std::endl;
-        return false;
-    }
-    return true;
-}
-
-/**
- * Determines if a layout should be auto generated based on SDK level. We do not
- * generate a layout if there is already a layout defined whose SDK version is greater than
- * the one we want to generate.
- */
-bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
-                                     const ResourceName& name, const ConfigDescription& config,
-                                     int sdkVersionToGenerate) {
-    assert(sdkVersionToGenerate > config.sdkVersion);
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = table->findResource(name);
-    assert(type && entry);
-
-    auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
-            [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
-        return lhs.config < config;
-    });
-
-    assert(iter != entry->values.end());
-    ++iter;
-
-    if (iter == entry->values.end()) {
-        return true;
-    }
-
-    ConfigDescription newConfig = config;
-    newConfig.sdkVersion = sdkVersionToGenerate;
-    return newConfig < iter->config;
-}
-
-bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-             const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
-             const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
-             proguard::KeepSet* keepSet) {
-    SourceLogger logger(item.source);
-    std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
-    if (!root) {
-        return false;
-    }
-
-    xml::FlattenOptions xmlOptions;
-    if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
-        xmlOptions.keepRawValues = true;
-    }
-
-    if (options.versionStylesAndLayouts) {
-        // We strip attributes that do not belong in this version of the resource.
-        // Non-version qualified resources have an implicit version 1 requirement.
-        xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
-    }
-
-    if (options.generateProguardRules) {
-        proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
-    }
-
-    BigBuffer outBuffer(1024);
-    Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
-                                                       item.originalPackage, resolver,
-                                                       xmlOptions, &outBuffer);
-    if (!minStrippedSdk) {
-        logger.error() << "failed to encode XML." << std::endl;
-        return false;
-    }
-
-    if (minStrippedSdk.value() > 0) {
-        // Something was stripped, so let's generate a new file
-        // with the version of the smallest SDK version stripped.
-        // We can only generate a versioned layout if there doesn't exist a layout
-        // with sdk version greater than the current one but less than the one we
-        // want to generate.
-        if (shouldGenerateVersionedResource(table, item.name, item.config,
-                    minStrippedSdk.value())) {
-            LinkItem newWork = item;
-            newWork.config.sdkVersion = minStrippedSdk.value();
-            outQueue->push(newWork);
-
-            if (!addFileReference(table, newWork)) {
-                Logger::error(options.output) << "failed to add auto-versioned resource '"
-                                              << newWork.name << "'." << std::endl;
-                return false;
-            }
-        }
-    }
-
-    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
-                nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write linked file '"
-                                      << buildFileReference(item) << "' to apk." << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
-    std::ifstream in(item.source.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(item.source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    BigBuffer outBuffer(4096);
-    std::string err;
-    Png png;
-    if (!png.process(item.source, in, &outBuffer, {}, &err)) {
-        Logger::error(item.source) << err << std::endl;
-        return false;
-    }
-
-    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
-                nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write compiled '" << item.source
-                                      << "' to apk." << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
-    if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
-                ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
-                                      << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
-                     const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
-                     const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
-    if (options.verbose) {
-        Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
-    }
-
-    std::ifstream in(options.manifest.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(options.manifest) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    SourceLogger logger(options.manifest);
-    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
-    if (!root) {
-        return false;
-    }
-
-    ManifestMerger merger({});
-    if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
-        return false;
-    }
-
-    for (const auto& entry : libApks) {
-        ZipFile* libApk = entry.second.apk.get();
-        const std::u16string& libPackage = entry.first->getPackage();
-        const Source& libSource = entry.second.source;
-
-        ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
-        if (!zipEntry) {
-            continue;
-        }
-
-        std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
-                libApk->uncompress(zipEntry));
-        assert(uncompressedData);
-
-        SourceLogger logger(libSource);
-        std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
-                                                          zipEntry->getUncompressedLen(), &logger);
-        if (!libRoot) {
-            return false;
-        }
-
-        if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
-            return false;
-        }
-    }
-
-    if (options.generateProguardRules) {
-        proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
-                                                  keepSet);
-    }
-
-    BigBuffer outBuffer(1024);
-    if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
-                resolver, {}, &outBuffer)) {
-        return false;
-    }
-
-    std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
-
-    android::ResXMLTree tree;
-    if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
-        return false;
-    }
-
-    ManifestValidator validator(table);
-    if (!validator.validate(options.manifest, &tree)) {
-        return false;
-    }
-
-    if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
-                ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
-                                      << std::endl;
-        return false;
-    }
-    return true;
-}
-
-static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
-                          const ConfigDescription& config) {
-    std::ifstream in(source.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
-    ResourceParser parser(table, source, config, xmlParser);
-    return parser.parse();
-}
-
-struct ResourcePathData {
-    std::u16string resourceDir;
-    std::u16string name;
-    std::string extension;
-    ConfigDescription config;
-};
-
-/**
- * Resource file paths are expected to look like:
- * [--/res/]type[-config]/name
- */
-static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
-    // TODO(adamlesinski): Use Windows path separator on windows.
-    std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
-    if (parts.size() < 2) {
-        Logger::error(source) << "bad resource path." << std::endl;
-        return {};
-    }
-
-    std::string& dir = parts[parts.size() - 2];
-    StringPiece dirStr = dir;
-
-    ConfigDescription config;
-    size_t dashPos = dir.find('-');
-    if (dashPos != std::string::npos) {
-        StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
-        if (!ConfigDescription::parse(configStr, &config)) {
-            Logger::error(source)
-                    << "invalid configuration '"
-                    << configStr
-                    << "'."
-                    << std::endl;
-            return {};
-        }
-        dirStr = dirStr.substr(0, dashPos);
-    }
-
-    std::string& filename = parts[parts.size() - 1];
-    StringPiece name = filename;
-    StringPiece extension;
-    size_t dotPos = filename.find('.');
-    if (dotPos != std::string::npos) {
-        extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
-        name = name.substr(0, dotPos);
-    }
-
-    return ResourcePathData{
-            util::utf8ToUtf16(dirStr),
-            util::utf8ToUtf16(name),
-            extension.toString(),
-            config
-    };
-}
-
-bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-                        const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
-    if (table->begin() != table->end()) {
-        BigBuffer buffer(1024);
-        TableFlattener flattener(flattenerOptions);
-        if (!flattener.flatten(&buffer, *table)) {
-            Logger::error() << "failed to flatten resource table." << std::endl;
-            return false;
-        }
-
-        if (options.verbose) {
-            Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
-                           << std::endl;
-        }
-
-        if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
-                android::NO_ERROR) {
-            Logger::note(options.output) << "failed to store resource table." << std::endl;
-            return false;
-        }
-    }
-    return true;
-}
-
-/**
- * For each FileReference in the table, adds a LinkItem to the link queue for processing.
- */
-static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
-                                   const std::shared_ptr<ResourceTable>& table,
-                                   const std::unique_ptr<ZipFile>& apk,
-                                   std::queue<LinkItem>* outLinkQueue) {
-    bool mangle = package != table->getPackage();
-    for (auto& type : *table) {
-        for (auto& entry : type->entries) {
-            ResourceName name = { package, type->type, entry->name };
-            if (mangle) {
-                NameMangler::mangle(table->getPackage(), &name.entry);
-            }
-
-            for (auto& value : entry->values) {
-                visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
-                    std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
-                    Source newSource = source;
-                    newSource.path += "/";
-                    newSource.path += pathUtf8;
-                    outLinkQueue->push(LinkItem{
-                            name, value.config, newSource, pathUtf8, apk.get(),
-                            table->getPackage() });
-                    // Now rewrite the file path.
-                    if (mangle) {
-                        ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
-                                    buildFileReference(name, value.config,
-                                                       getExtension<char>(pathUtf8))));
-                    }
-                });
-            }
-        }
-    }
-}
-
-static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
-        ZipFile::kOpenReadWrite;
-
-bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
-          const std::shared_ptr<IResolver>& resolver) {
-    std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
-    std::unordered_set<std::u16string> linkedPackages;
-
-    // Populate the linkedPackages with our own.
-    linkedPackages.insert(options.appInfo.package);
-
-    // Load all APK files.
-    for (const Source& source : options.input) {
-        std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
-        if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
-            Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
-            return false;
-        }
-
-        std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
-
-        ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
-        if (!entry) {
-            Logger::error(source) << "missing 'resources.arsc'." << std::endl;
-            return false;
-        }
-
-        std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
-                zipFile->uncompress(entry));
-        assert(uncompressedData);
-
-        BinaryResourceParser parser(table, resolver, source, options.appInfo.package, 
-                                    uncompressedData.get(), entry->getUncompressedLen());
-        if (!parser.parse()) {
-            return false;
-        }
-
-        // Keep track of where this table came from.
-        apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
-
-        // Add the package to the set of linked packages.
-        linkedPackages.insert(table->getPackage());
-    }
-
-    std::queue<LinkItem> linkQueue;
-    for (auto& p : apkFiles) {
-        const std::shared_ptr<ResourceTable>& inTable = p.first;
-
-        // Collect all FileReferences and add them to the queue for processing.
-        addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
-                               &linkQueue);
-
-        // Merge the tables.
-        if (!outTable->merge(std::move(*inTable))) {
-            return false;
-        }
-    }
-
-    // Version all styles referencing attributes outside of their specified SDK version.
-    if (options.versionStylesAndLayouts) {
-        versionStylesForCompat(outTable);
-    }
-
-    {
-        // Now that everything is merged, let's link it.
-        Linker::Options linkerOptions;
-        if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
-            linkerOptions.linkResourceIds = false;
-        }
-        Linker linker(outTable, resolver, linkerOptions);
-        if (!linker.linkAndValidate()) {
-            return false;
-        }
-
-        // Verify that all symbols exist.
-        const auto& unresolvedRefs = linker.getUnresolvedReferences();
-        if (!unresolvedRefs.empty()) {
-            for (const auto& entry : unresolvedRefs) {
-                for (const auto& source : entry.second) {
-                    Logger::error(source) << "unresolved symbol '" << entry.first << "'."
-                                          << std::endl;
-                }
-            }
-            return false;
-        }
-    }
-
-    // Open the output APK file for writing.
-    ZipFile outApk;
-    if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
-        return false;
-    }
-
-    proguard::KeepSet keepSet;
-
-    android::ResTable binTable;
-    if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
-        return false;
-    }
-
-    for (; !linkQueue.empty(); linkQueue.pop()) {
-        const LinkItem& item = linkQueue.front();
-
-        assert(!item.originalPackage.empty());
-        ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
-        if (!entry) {
-            Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
-                                       << std::endl;
-            return false;
-        }
-
-        if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
-            void* uncompressedData = item.apk->uncompress(entry);
-            assert(uncompressedData);
-
-            if (!linkXml(options, outTable, resolver, item, uncompressedData,
-                        entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
-                Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
-                                              << std::endl;
-                return false;
-            }
-        } else {
-            if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
-                    android::NO_ERROR) {
-                Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
-                                              << std::endl;
-                return false;
-            }
-        }
-    }
-
-    // Generate the Java class file.
-    if (options.generateJavaClass) {
-        JavaClassGenerator::Options javaOptions;
-        if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
-            javaOptions.useFinal = false;
-        }
-        JavaClassGenerator generator(outTable, javaOptions);
-
-        for (const std::u16string& package : linkedPackages) {
-            Source outPath = options.generateJavaClass.value();
-
-            // Build the output directory from the package name.
-            // Eg. com.android.app -> com/android/app
-            const std::string packageUtf8 = util::utf16ToUtf8(package);
-            for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
-                appendPath(&outPath.path, part);
-            }
-
-            if (!mkdirs(outPath.path)) {
-                Logger::error(outPath) << strerror(errno) << std::endl;
-                return false;
-            }
-
-            appendPath(&outPath.path, "R.java");
-
-            if (options.verbose) {
-                Logger::note(outPath) << "writing Java symbols." << std::endl;
-            }
-
-            std::ofstream fout(outPath.path);
-            if (!fout) {
-                Logger::error(outPath) << strerror(errno) << std::endl;
-                return false;
-            }
-
-            if (!generator.generate(package, fout)) {
-                Logger::error(outPath) << generator.getError() << "." << std::endl;
-                return false;
-            }
-        }
-    }
-
-    // Generate the Proguard rules file.
-    if (options.generateProguardRules) {
-        const Source& outPath = options.generateProguardRules.value();
-
-        if (options.verbose) {
-            Logger::note(outPath) << "writing proguard rules." << std::endl;
-        }
-
-        std::ofstream fout(outPath.path);
-        if (!fout) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        if (!proguard::writeKeepSet(&fout, keepSet)) {
-            Logger::error(outPath) << "failed to write proguard rules." << std::endl;
-            return false;
-        }
-    }
-
-    outTable->getValueStringPool().prune();
-    outTable->getValueStringPool().sort(
-            [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
-                if (a.context.priority < b.context.priority) {
-                    return true;
-                }
-
-                if (a.context.priority > b.context.priority) {
-                    return false;
-                }
-                return a.value < b.value;
-            });
-
-
-    // Flatten the resource table.
-    TableFlattener::Options flattenerOptions;
-    if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
-        flattenerOptions.useExtendedChunks = false;
-    }
-
-    if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
-        return false;
-    }
-
-    outApk.flush();
-    return true;
-}
-
-bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
-             const std::shared_ptr<IResolver>& resolver) {
-    std::queue<CompileItem> compileQueue;
-    bool error = false;
-
-    // Compile all the resource files passed in on the command line.
-    for (const Source& source : options.input) {
-        // Need to parse the resource type/config/filename.
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
-            return false;
-        }
-
-        const ResourcePathData& pathData = maybePathData.value();
-        if (pathData.resourceDir == u"values") {
-            // The file is in the values directory, which means its contents will
-            // go into the resource table.
-            if (options.verbose) {
-                Logger::note(source) << "compiling values." << std::endl;
-            }
-
-            error |= !compileValues(table, source, pathData.config);
-        } else {
-            // The file is in a directory like 'layout' or 'drawable'. Find out
-            // the type.
-            const ResourceType* type = parseResourceType(pathData.resourceDir);
-            if (!type) {
-                Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
-                                      << std::endl;
-                return false;
-            }
-
-            compileQueue.push(CompileItem{
-                    ResourceName{ table->getPackage(), *type, pathData.name },
-                    pathData.config,
-                    source,
-                    pathData.extension
-            });
-        }
-    }
-
-    if (error) {
-        return false;
-    }
-    // Open the output APK file for writing.
-    ZipFile outApk;
-    if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
-        Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
-        return false;
-    }
-
-    // Compile each file.
-    for (; !compileQueue.empty(); compileQueue.pop()) {
-        const CompileItem& item = compileQueue.front();
-
-        // Add the file name to the resource table.
-        error |= !addFileReference(table, item);
-
-        if (item.extension == "xml") {
-            error |= !compileXml(options, table, item, &outApk);
-        } else if (item.extension == "png" || item.extension == "9.png") {
-            error |= !compilePng(options, item, &outApk);
-        } else {
-            error |= !copyFile(options, item, &outApk);
-        }
-    }
-
-    if (error) {
-        return false;
-    }
-
-    // Link and assign resource IDs.
-    Linker linker(table, resolver, {});
-    if (!linker.linkAndValidate()) {
-        return false;
-    }
-
-    // Flatten the resource table.
-    if (!writeResourceTable(options, table, {}, &outApk)) {
-        return false;
-    }
-
-    outApk.flush();
-    return true;
-}
-
-bool loadAppInfo(const Source& source, AppInfo* outInfo) {
-    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
-    if (!ifs) {
-        Logger::error(source) << strerror(errno) << std::endl;
-        return false;
-    }
-
-    ManifestParser parser;
-    std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
-    return parser.parse(source, pullParser, outInfo);
-}
-
-static void printCommandsAndDie() {
-    std::cerr << "The following commands are supported:" << std::endl << std::endl;
-    std::cerr << "compile       compiles a subset of resources" << std::endl;
-    std::cerr << "link          links together compiled resources and libraries" << std::endl;
-    std::cerr << "dump          dumps resource contents to to standard out" << std::endl;
-    std::cerr << std::endl;
-    std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
-              << std::endl;
-    exit(1);
-}
-
-static AaptOptions prepareArgs(int argc, char** argv) {
-    if (argc < 2) {
-        std::cerr << "no command specified." << std::endl << std::endl;
-        printCommandsAndDie();
-    }
-
-    const StringPiece command(argv[1]);
-    argc -= 2;
-    argv += 2;
-
-    AaptOptions options;
-
-    if (command == "--version" || command == "version") {
-        std::cout << kAaptVersionStr << std::endl;
-        exit(0);
-    } else if (command == "link") {
-        options.phase = AaptOptions::Phase::Link;
-    } else if (command == "compile") {
-        options.phase = AaptOptions::Phase::Compile;
-    } else if (command == "dump") {
-        options.phase = AaptOptions::Phase::Dump;
-    } else if (command == "dump-style-graph") {
-        options.phase = AaptOptions::Phase::DumpStyleGraph;
-    } else {
-        std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
-        printCommandsAndDie();
-    }
-
-    bool isStaticLib = false;
-    if (options.phase == AaptOptions::Phase::Link) {
-        flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
-                [&options](const StringPiece& arg) {
-                    options.manifest = Source{ arg.toString() };
-                });
-
-        flag::optionalFlag("-I", "add an Android APK to link against",
-                [&options](const StringPiece& arg) {
-                    options.libraries.push_back(Source{ arg.toString() });
-                });
-
-        flag::optionalFlag("--java", "directory in which to generate R.java",
-                [&options](const StringPiece& arg) {
-                    options.generateJavaClass = Source{ arg.toString() };
-                });
-
-        flag::optionalFlag("--proguard", "file in which to output proguard rules",
-                [&options](const StringPiece& arg) {
-                    options.generateProguardRules = Source{ arg.toString() };
-                });
-
-        flag::optionalSwitch("--static-lib", "generate a static Android library", true,
-                             &isStaticLib);
-
-        flag::optionalFlag("--binding", "Output directory for binding XML files",
-                [&options](const StringPiece& arg) {
-                    options.bindingOutput = Source{ arg.toString() };
-                });
-        flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
-                             false, &options.versionStylesAndLayouts);
-    }
-
-    if (options.phase == AaptOptions::Phase::Compile ||
-            options.phase == AaptOptions::Phase::Link) {
-        // Common flags for all steps.
-        flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
-            options.output = Source{ arg.toString() };
-        });
-    }
-
-    if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
-        flag::requiredFlag("--style", "Name of the style to dump",
-                [&options](const StringPiece& arg, std::string* outError) -> bool {
-                    Reference styleReference;
-                    if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
-                                &styleReference, outError)) {
-                        return false;
-                    }
-                    options.dumpStyleTarget = styleReference.name;
-                    return true;
-                });
-    }
-
-    bool help = false;
-    flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
-    flag::optionalSwitch("-h", "displays this help menu", true, &help);
-
-    // Build the command string for output (eg. "aapt2 compile").
-    std::string fullCommand = "aapt2";
-    fullCommand += " ";
-    fullCommand += command.toString();
-
-    // Actually read the command line flags.
-    flag::parse(argc, argv, fullCommand);
-
-    if (help) {
-        flag::usageAndDie(fullCommand);
-    }
-
-    if (isStaticLib) {
-        options.packageType = AaptOptions::PackageType::StaticLibrary;
-    }
-
-    // Copy all the remaining arguments.
-    for (const std::string& arg : flag::getArgs()) {
-        options.input.push_back(Source{ arg });
-    }
-    return options;
-}
-
-static bool doDump(const AaptOptions& options) {
-    for (const Source& source : options.input) {
-        std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
-        if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
-            Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
-            return false;
-        }
-
-        std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
-        std::shared_ptr<ResourceTableResolver> resolver =
-                std::make_shared<ResourceTableResolver>(
-                        table, std::vector<std::shared_ptr<const android::AssetManager>>());
-
-        ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
-        if (!entry) {
-            Logger::error(source) << "missing 'resources.arsc'." << std::endl;
-            return false;
-        }
-
-        std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
-                zipFile->uncompress(entry));
-        assert(uncompressedData);
-
-        BinaryResourceParser parser(table, resolver, source, {}, uncompressedData.get(),
-                                    entry->getUncompressedLen());
-        if (!parser.parse()) {
-            return false;
-        }
-
-        if (options.phase == AaptOptions::Phase::Dump) {
-            Debug::printTable(table);
-        } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
-            Debug::printStyleGraph(table, options.dumpStyleTarget);
-        }
-    }
-    return true;
-}
+} // namespace aapt
 
 int main(int argc, char** argv) {
-    Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
-    AaptOptions options = prepareArgs(argc, argv);
+    if (argc >= 2) {
+        argv += 1;
+        argc -= 1;
 
-    if (options.phase == AaptOptions::Phase::Dump ||
-            options.phase == AaptOptions::Phase::DumpStyleGraph) {
-        if (!doDump(options)) {
-            return 1;
-        }
-        return 0;
-    }
-
-    // If we specified a manifest, go ahead and load the package name from the manifest.
-    if (!options.manifest.path.empty()) {
-        if (!loadAppInfo(options.manifest, &options.appInfo)) {
-            return false;
+        std::vector<aapt::StringPiece> args;
+        for (int i = 1; i < argc; i++) {
+            args.push_back(argv[i]);
         }
 
-        if (options.appInfo.package.empty()) {
-            Logger::error() << "no package name specified." << std::endl;
-            return false;
+        aapt::StringPiece command(argv[0]);
+        if (command == "compile" || command == "c") {
+            return aapt::compile(args);
+        } else if (command == "link" || command == "l") {
+            return aapt::link(args);
         }
-    }
-
-    // Every phase needs a resource table.
-    std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
-
-    // The package name is empty when in the compile phase.
-    table->setPackage(options.appInfo.package);
-    if (options.appInfo.package == u"android") {
-        table->setPackageId(0x01);
+        std::cerr << "unknown command '" << command << "'\n";
     } else {
-        table->setPackageId(0x7f);
+        std::cerr << "no command specified\n";
     }
 
-    // Load the included libraries.
-    std::vector<std::shared_ptr<const android::AssetManager>> sources;
-    for (const Source& source : options.libraries) {
-        std::shared_ptr<android::AssetManager> assetManager =
-                std::make_shared<android::AssetManager>();
-        int32_t cookie;
-        if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
-            Logger::error(source) << "failed to load library." << std::endl;
-            return false;
-        }
-
-        if (cookie == 0) {
-            Logger::error(source) << "failed to load library." << std::endl;
-            return false;
-        }
-        sources.push_back(assetManager);
-    }
-
-    // Make the resolver that will cache IDs for us.
-    std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
-            table, sources);
-
-    if (options.phase == AaptOptions::Phase::Compile) {
-        if (!compile(options, table, resolver)) {
-            Logger::error() << "aapt exiting with failures." << std::endl;
-            return 1;
-        }
-    } else if (options.phase == AaptOptions::Phase::Link) {
-        if (!link(options, table, resolver)) {
-            Logger::error() << "aapt exiting with failures." << std::endl;
-            return 1;
-        }
-    }
-    return 0;
+    std::cerr << "\nusage: aapt2 [compile|link] ..." << std::endl;
+    return 1;
 }
diff --git a/tools/aapt2/ManifestMerger.cpp b/tools/aapt2/ManifestMerger.cpp
deleted file mode 100644
index 71d3424..0000000
--- a/tools/aapt2/ManifestMerger.cpp
+++ /dev/null
@@ -1,376 +0,0 @@
-#include "ManifestMerger.h"
-#include "Maybe.h"
-#include "ResourceParser.h"
-#include "Source.h"
-#include "Util.h"
-#include "XmlPullParser.h"
-
-#include <iostream>
-#include <memory>
-#include <set>
-#include <string>
-
-namespace aapt {
-
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
-static xml::Element* findManifest(xml::Node* root) {
-    if (!root) {
-        return nullptr;
-    }
-
-    while (root->type == xml::NodeType::kNamespace) {
-        if (root->children.empty()) {
-            break;
-        }
-        root = root->children[0].get();
-    }
-
-    if (root && root->type == xml::NodeType::kElement) {
-        xml::Element* el = static_cast<xml::Element*>(root);
-        if (el->namespaceUri.empty() && el->name == u"manifest") {
-            return el;
-        }
-    }
-    return nullptr;
-}
-
-static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) {
-    xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name");
-    if (!attrKey) {
-        return nullptr;
-    }
-    return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey);
-}
-
-static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) {
-    return std::tie(lhs.namespaceUri, lhs.name, lhs.value)
-            < std::tie(rhs.namespaceUri, rhs.name, rhs.value);
-}
-
-static int compare(xml::Element* lhs, xml::Element* rhs) {
-    int diff = lhs->attributes.size() - rhs->attributes.size();
-    if (diff != 0) {
-        return diff;
-    }
-
-    std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess);
-    lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end());
-    for (auto& attr : rhs->attributes) {
-        if (lhsAttrs.erase(attr) == 0) {
-            // The rhs attribute is not in the left.
-            return -1;
-        }
-    }
-
-    if (!lhsAttrs.empty()) {
-        // The lhs has attributes not in the rhs.
-        return 1;
-    }
-    return 0;
-}
-
-ManifestMerger::ManifestMerger(const Options& options) :
-        mOptions(options), mAppLogger({}), mLogger({}) {
-}
-
-bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package,
-                                    std::unique_ptr<xml::Node> root) {
-
-    mAppLogger = SourceLogger{ source };
-    mRoot = std::move(root);
-    return true;
-}
-
-bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) {
-    if (compare(elA, elB) != 0) {
-        mLogger.error(elB->lineNumber)
-                << "library tag '" << elB->name << "' conflicts with app tag."
-                << std::endl;
-        mAppLogger.note(elA->lineNumber)
-                << "app tag '" << elA->name << "' defined here."
-                << std::endl;
-        return false;
-    }
-
-    std::vector<xml::Element*> childrenA = elA->getChildElements();
-    std::vector<xml::Element*> childrenB = elB->getChildElements();
-
-    if (childrenA.size() != childrenB.size()) {
-        mLogger.error(elB->lineNumber)
-                << "library tag '" << elB->name << "' children conflict with app tag."
-                << std::endl;
-        mAppLogger.note(elA->lineNumber)
-                << "app tag '" << elA->name << "' defined here."
-                << std::endl;
-        return false;
-    }
-
-    auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool {
-        return compare(lhs, rhs) < 0;
-    };
-
-    std::sort(childrenA.begin(), childrenA.end(), cmp);
-    std::sort(childrenB.begin(), childrenB.end(), cmp);
-
-    for (size_t i = 0; i < childrenA.size(); i++) {
-        if (!checkEqual(childrenA[i], childrenB[i])) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) {
-    if (!elA) {
-        parentA->addChild(elB->clone());
-        return true;
-    }
-    return checkEqual(elA, elB);
-}
-
-bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA,
-                                         xml::Element* elB) {
-    if (!elA) {
-        parentA->addChild(elB->clone());
-        return true;
-    }
-
-    xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required");
-    xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required");
-    bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE");
-    bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE");
-    if (!requiredA && requiredB) {
-        if (reqA) {
-            *reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" };
-        } else {
-            elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" });
-        }
-    }
-    return true;
-}
-
-static int findIntegerValue(xml::Attribute* attr, int defaultValue) {
-    if (attr) {
-        std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value);
-        if (integer) {
-            return integer->value.data;
-        }
-    }
-    return defaultValue;
-}
-
-bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) {
-    bool error = false;
-    xml::Attribute* minAttrA = nullptr;
-    xml::Attribute* minAttrB = nullptr;
-    if (elA) {
-        minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion");
-    }
-
-    if (elB) {
-        minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion");
-    }
-
-    int minSdkA = findIntegerValue(minAttrA, 1);
-    int minSdkB = findIntegerValue(minAttrB, 1);
-
-    if (minSdkA < minSdkB) {
-        std::ostream* out;
-        if (minAttrA) {
-            out = &(mAppLogger.error(elA->lineNumber) << "app declares ");
-        } else if (elA) {
-            out = &(mAppLogger.error(elA->lineNumber) << "app has implied ");
-        } else {
-            out = &(mAppLogger.error() << "app has implied ");
-        }
-
-        *out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version."
-             << std::endl;
-
-        // elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't.
-        mLogger.note(elB->lineNumber)
-                << "library declares minSdkVersion=" << minSdkB << "."
-                << std::endl;
-        error = true;
-    }
-
-    xml::Attribute* targetAttrA = nullptr;
-    xml::Attribute* targetAttrB = nullptr;
-
-    if (elA) {
-        targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion");
-    }
-
-    if (elB) {
-        targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion");
-    }
-
-    int targetSdkA = findIntegerValue(targetAttrA, minSdkA);
-    int targetSdkB = findIntegerValue(targetAttrB, minSdkB);
-
-    if (targetSdkA < targetSdkB) {
-        std::ostream* out;
-        if (targetAttrA) {
-            out = &(mAppLogger.warn(elA->lineNumber) << "app declares ");
-        } else if (elA) {
-            out = &(mAppLogger.warn(elA->lineNumber) << "app has implied ");
-        } else {
-            out = &(mAppLogger.warn() << "app has implied ");
-        }
-
-        *out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK "
-             << targetSdkB << "." << std::endl;
-
-        mLogger.note(elB->lineNumber)
-                << "library declares targetSdkVersion=" << targetSdkB << "."
-                << std::endl;
-        error = true;
-    }
-    return !error;
-}
-
-bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) {
-    if (!applicationA || !applicationB) {
-        return true;
-    }
-
-    bool error = false;
-
-    // First make sure that the names are identical.
-    xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name");
-    xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name");
-    if (nameB) {
-        if (!nameA) {
-            applicationA->attributes.push_back(*nameB);
-        } else if (nameA->value != nameB->value) {
-            mLogger.error(applicationB->lineNumber)
-                    << "conflicting application name '"
-                    << nameB->value
-                    << "'." << std::endl;
-            mAppLogger.note(applicationA->lineNumber)
-                    << "application defines application name '"
-                    << nameA->value
-                    << "'." << std::endl;
-            error = true;
-        }
-    }
-
-    // Now we descend into the activity/receiver/service/provider tags
-    for (xml::Element* elB : applicationB->getChildElements()) {
-        if (!elB->namespaceUri.empty()) {
-            continue;
-        }
-
-        if (elB->name == u"activity" || elB->name == u"activity-alias"
-                || elB->name == u"service" || elB->name == u"receiver"
-                || elB->name == u"provider" || elB->name == u"meta-data") {
-            xml::Element* elA = findChildWithSameName(applicationA, elB);
-            error |= !mergeNewOrEqual(applicationA, elA, elB);
-        } else if (elB->name == u"uses-library") {
-            xml::Element* elA = findChildWithSameName(applicationA, elB);
-            error |= !mergePreferRequired(applicationA, elA, elB);
-        }
-    }
-    return !error;
-}
-
-bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package,
-                                          std::unique_ptr<xml::Node> libRoot) {
-    mLogger = SourceLogger{ source };
-    xml::Element* manifestA = findManifest(mRoot.get());
-    xml::Element* manifestB = findManifest(libRoot.get());
-    if (!manifestA) {
-        mAppLogger.error() << "missing manifest tag." << std::endl;
-        return false;
-    }
-
-    if (!manifestB) {
-        mLogger.error() << "library missing manifest tag." << std::endl;
-        return false;
-    }
-
-    bool error = false;
-
-    // Do <application> first.
-    xml::Element* applicationA = manifestA->findChild({}, u"application");
-    xml::Element* applicationB = manifestB->findChild({}, u"application");
-    error |= !mergeApplication(applicationA, applicationB);
-
-    // Do <uses-sdk> next.
-    xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk");
-    xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk");
-    error |= !mergeUsesSdk(usesSdkA, usesSdkB);
-
-    for (xml::Element* elB : manifestB->getChildElements()) {
-        if (!elB->namespaceUri.empty()) {
-            continue;
-        }
-
-        if (elB->name == u"uses-permission" || elB->name == u"permission"
-                || elB->name == u"permission-group" || elB->name == u"permission-tree") {
-            xml::Element* elA = findChildWithSameName(manifestA, elB);
-            error |= !mergeNewOrEqual(manifestA, elA, elB);
-        } else if (elB->name == u"uses-feature") {
-            xml::Element* elA = findChildWithSameName(manifestA, elB);
-            error |= !mergePreferRequired(manifestA, elA, elB);
-        } else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen"
-                || elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") {
-            xml::Element* elA = findChildWithSameName(manifestA, elB);
-            error |= !checkEqual(elA, elB);
-        }
-    }
-    return !error;
-}
-
-static void printMerged(xml::Node* node, int depth) {
-    std::string indent;
-    for (int i = 0; i < depth; i++) {
-        indent += "  ";
-    }
-
-    switch (node->type) {
-        case xml::NodeType::kNamespace:
-            std::cerr << indent << "N: "
-                      << "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix
-                      << "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri
-                      << "\"\n";
-            break;
-
-        case xml::NodeType::kElement:
-            std::cerr << indent << "E: "
-                      << static_cast<xml::Element*>(node)->namespaceUri
-                      << ":" << static_cast<xml::Element*>(node)->name
-                      << "\n";
-            for (const auto& attr : static_cast<xml::Element*>(node)->attributes) {
-                std::cerr << indent << "  A: "
-                          << attr.namespaceUri
-                          << ":" << attr.name
-                          << "=\"" << attr.value << "\"\n";
-            }
-            break;
-
-        case xml::NodeType::kText:
-            std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n";
-            break;
-    }
-
-    for (auto& child : node->children) {
-        printMerged(child.get(), depth + 1);
-    }
-}
-
-xml::Node* ManifestMerger::getMergedXml() {
-    return mRoot.get();
-}
-
-bool ManifestMerger::printMerged() {
-    if (!mRoot) {
-        return false;
-    }
-
-    ::aapt::printMerged(mRoot.get(), 0);
-    return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestMerger.h b/tools/aapt2/ManifestMerger.h
deleted file mode 100644
index c6219db..0000000
--- a/tools/aapt2/ManifestMerger.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef AAPT_MANIFEST_MERGER_H
-#define AAPT_MANIFEST_MERGER_H
-
-#include "Logger.h"
-#include "Source.h"
-#include "XmlDom.h"
-
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-class ManifestMerger {
-public:
-    struct Options {
-    };
-
-    ManifestMerger(const Options& options);
-
-    bool setAppManifest(const Source& source, const std::u16string& package,
-                        std::unique_ptr<xml::Node> root);
-
-    bool mergeLibraryManifest(const Source& source, const std::u16string& package,
-                              std::unique_ptr<xml::Node> libRoot);
-
-    xml::Node* getMergedXml();
-
-    bool printMerged();
-
-private:
-    bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
-    bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
-    bool checkEqual(xml::Element* elA, xml::Element* elB);
-    bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB);
-    bool mergeUsesSdk(xml::Element* elA, xml::Element* elB);
-
-    Options mOptions;
-    std::unique_ptr<xml::Node> mRoot;
-    SourceLogger mAppLogger;
-    SourceLogger mLogger;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_MERGER_H
diff --git a/tools/aapt2/ManifestMerger_test.cpp b/tools/aapt2/ManifestMerger_test.cpp
deleted file mode 100644
index 6838253..0000000
--- a/tools/aapt2/ManifestMerger_test.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ManifestMerger.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-feature android:name="android.hardware.GPS" android:required="false" />
-    <application android:name="com.android.library.Application">
-        <activity android:name="com.android.example.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-        <service android:name="com.android.library.Service">
-            <intent-filter>
-                <action android:name="com.android.library.intent.action.SYNC" />
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
-)EOF";
-
-constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-feature android:name="android.hardware.GPS" />
-    <uses-permission android:name="android.permission.GPS" />
-    <application android:name="com.android.library.Application">
-        <service android:name="com.android.library.Service">
-            <intent-filter>
-                <action android:name="com.android.library.intent.action.SYNC" />
-            </intent-filter>
-        </service>
-        <provider android:name="com.android.library.DocumentProvider"
-                  android:authorities="com.android.library.documents"
-                  android:grantUriPermission="true"
-                  android:exported="true"
-                  android:permission="android.permission.MANAGE_DOCUMENTS"
-                  android:enabled="@bool/atLeastKitKat">
-            <intent-filter>
-                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
-            </intent-filter>
-        </provider>
-    </application>
-</manifest>
-)EOF";
-
-constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-feature android:name="android.hardware.GPS" />
-    <uses-permission android:name="android.permission.GPS" />
-    <application android:name="com.android.library.Application2">
-        <service android:name="com.android.library.Service">
-            <intent-filter>
-                <action android:name="com.android.library.intent.action.SYNC_ACTION" />
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
-)EOF";
-
-TEST(ManifestMergerTest, MergeManifestsSuccess) {
-    std::stringstream inA(kAppManifest);
-    std::stringstream inB(kLibManifest);
-
-    const Source sourceA = { "AndroidManifest.xml" };
-    const Source sourceB = { "lib.apk/AndroidManifest.xml" };
-    SourceLogger loggerA(sourceA);
-    SourceLogger loggerB(sourceB);
-
-    ManifestMerger merger({});
-    EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
-                xml::inflate(&inA, &loggerA)));
-    EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
-                xml::inflate(&inB, &loggerB)));
-}
-
-TEST(ManifestMergerTest, MergeManifestFail) {
-    std::stringstream inA(kAppManifest);
-    std::stringstream inB(kBadLibManifest);
-
-    const Source sourceA = { "AndroidManifest.xml" };
-    const Source sourceB = { "lib.apk/AndroidManifest.xml" };
-    SourceLogger loggerA(sourceA);
-    SourceLogger loggerB(sourceB);
-
-    ManifestMerger merger({});
-    EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
-                xml::inflate(&inA, &loggerA)));
-    EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
-                xml::inflate(&inB, &loggerB)));
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.cpp b/tools/aapt2/ManifestParser.cpp
deleted file mode 100644
index b8f0a43..0000000
--- a/tools/aapt2/ManifestParser.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "AppInfo.h"
-#include "Logger.h"
-#include "ManifestParser.h"
-#include "Source.h"
-#include "XmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser,
-                           AppInfo* outInfo) {
-    SourceLogger logger = { source };
-
-    int depth = 0;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
-        XmlPullParser::Event event = parser->getEvent();
-        if (event == XmlPullParser::Event::kEndElement) {
-            depth--;
-            continue;
-        } else if (event != XmlPullParser::Event::kStartElement) {
-            continue;
-        }
-
-        depth++;
-
-        const std::u16string& element = parser->getElementName();
-        if (depth == 1) {
-            if (element == u"manifest") {
-                if (!parseManifest(logger, parser, outInfo)) {
-                    return false;
-                }
-            } else {
-                logger.error()
-                        << "unexpected top-level element '"
-                        << element
-                        << "'."
-                        << std::endl;
-                return false;
-            }
-        } else {
-            XmlPullParser::skipCurrentElement(parser.get());
-        }
-    }
-
-    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
-            logger.error(parser->getLineNumber())
-                << "failed to parse manifest: "
-                << parser->getLastError()
-                << "."
-                << std::endl;
-        return false;
-    }
-    return true;
-}
-
-bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
-                                   AppInfo* outInfo) {
-    auto attrIter = parser->findAttribute(u"", u"package");
-    if (attrIter == parser->endAttributes() || attrIter->value.empty()) {
-        logger.error() << "no 'package' attribute found for element <manifest>." << std::endl;
-        return false;
-    }
-    outInfo->package = attrIter->value;
-    return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestParser.h b/tools/aapt2/ManifestParser.h
deleted file mode 100644
index f2e43d4..0000000
--- a/tools/aapt2/ManifestParser.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_MANIFEST_PARSER_H
-#define AAPT_MANIFEST_PARSER_H
-
-#include "AppInfo.h"
-#include "Logger.h"
-#include "Source.h"
-#include "XmlPullParser.h"
-
-namespace aapt {
-
-/*
- * Parses an AndroidManifest.xml file and fills in an AppInfo structure with
- * app data.
- */
-class ManifestParser {
-public:
-    ManifestParser() = default;
-    ManifestParser(const ManifestParser&) = delete;
-
-    bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo);
-
-private:
-    bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
-                       AppInfo* outInfo);
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_PARSER_H
diff --git a/tools/aapt2/ManifestParser_test.cpp b/tools/aapt2/ManifestParser_test.cpp
deleted file mode 100644
index be3a6fb..0000000
--- a/tools/aapt2/ManifestParser_test.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "AppInfo.h"
-#include "ManifestParser.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(ManifestParserTest, FindPackage) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
-             "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-             "package=\"android\">\n"
-             "</manifest>\n";
-
-    ManifestParser parser;
-    AppInfo info;
-    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
-    ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info));
-
-    EXPECT_EQ(std::u16string(u"android"), info.package);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp
index 123b9fa..9f971fb 100644
--- a/tools/aapt2/ManifestValidator.cpp
+++ b/tools/aapt2/ManifestValidator.cpp
@@ -16,9 +16,9 @@
 
 #include "Logger.h"
 #include "ManifestValidator.h"
-#include "Maybe.h"
+#include "util/Maybe.h"
 #include "Source.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
index 3188784..1a7f48e 100644
--- a/tools/aapt2/ManifestValidator.h
+++ b/tools/aapt2/ManifestValidator.h
@@ -18,9 +18,9 @@
 #define AAPT_MANIFEST_VALIDATOR_H
 
 #include "Logger.h"
-#include "Maybe.h"
+#include "util/Maybe.h"
 #include "Source.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <androidfw/ResourceTypes.h>
 
diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h
deleted file mode 100644
index 0c9b954..0000000
--- a/tools/aapt2/MockResolver.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_MOCK_RESOLVER_H
-#define AAPT_MOCK_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "StringPiece.h"
-
-#include <map>
-#include <string>
-
-namespace aapt {
-
-struct MockResolver : public IResolver {
-    MockResolver(const std::shared_ptr<ResourceTable>& table,
-                 const std::map<ResourceName, ResourceId>& items) :
-            mResolver(std::make_shared<ResourceTableResolver>(
-                    table, std::vector<std::shared_ptr<const android::AssetManager>>())),
-            mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) {
-    }
-
-    virtual Maybe<ResourceId> findId(const ResourceName& name) override {
-        Maybe<ResourceId> result = mResolver->findId(name);
-        if (result) {
-            return result;
-        }
-
-        const auto iter = mItems.find(name);
-        if (iter != mItems.end()) {
-            return iter->second;
-        }
-        return {};
-    }
-
-    virtual Maybe<Entry> findAttribute(const ResourceName& name) override {
-        Maybe<Entry> tableResult = mResolver->findAttribute(name);
-        if (tableResult) {
-            return tableResult;
-        }
-
-        Maybe<ResourceId> result = findId(name);
-        if (result) {
-            if (name.type == ResourceType::kAttr) {
-                return Entry{ result.value(), &mAttr };
-            } else {
-                return Entry{ result.value() };
-            }
-        }
-        return {};
-    }
-
-    virtual Maybe<ResourceName> findName(ResourceId resId) override {
-        Maybe<ResourceName> result = mResolver->findName(resId);
-        if (result) {
-            return result;
-        }
-
-        for (auto& p : mItems) {
-            if (p.second == resId) {
-                return p.first;
-            }
-        }
-        return {};
-    }
-
-private:
-    std::shared_ptr<ResourceTableResolver> mResolver;
-    Attribute mAttr;
-    std::map<ResourceName, ResourceId> mItems;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MOCK_RESOLVER_H
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 1e15e20..6d752bb 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -17,19 +17,63 @@
 #ifndef AAPT_NAME_MANGLER_H
 #define AAPT_NAME_MANGLER_H
 
+#include "Resource.h"
+
+#include "util/Maybe.h"
+
+#include <set>
 #include <string>
 
 namespace aapt {
 
-struct NameMangler {
+struct NameManglerPolicy {
     /**
-     * Mangles the name in `outName` with the `package` and stores the mangled
-     * result in `outName`. The mangled name should contain symbols that are
-     * illegal to define in XML, so that there will never be name mangling
-     * collisions.
+     * Represents the package we are trying to build. References pointing
+     * to this package are not mangled, and mangled references inherit this package name.
      */
-    static void mangle(const std::u16string& package, std::u16string* outName) {
-        *outName = package + u"$" + *outName;
+    std::u16string targetPackageName;
+
+    /**
+     * We must know which references to mangle, and which to keep (android vs. com.android.support).
+     */
+    std::set<std::u16string> packagesToMangle;
+};
+
+class NameMangler {
+private:
+    NameManglerPolicy mPolicy;
+
+public:
+    NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
+    }
+
+    Maybe<ResourceName> mangleName(const ResourceName& name) {
+        if (mPolicy.targetPackageName == name.package ||
+                mPolicy.packagesToMangle.count(name.package) == 0) {
+            return {};
+        }
+
+        return ResourceName{
+                mPolicy.targetPackageName,
+                name.type,
+                mangleEntry(name.package, name.entry)
+        };
+    }
+
+    bool shouldMangle(const std::u16string& package) {
+        if (package.empty() || mPolicy.targetPackageName == package) {
+            return false;
+        }
+        return mPolicy.packagesToMangle.count(package) != 0;
+    }
+
+    /**
+     * Returns a mangled name that is a combination of `name` and `package`.
+     * The mangled name should contain symbols that are illegal to define in XML,
+     * so that there will never be name mangling collisions.
+     */
+    static std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) {
+        return package + u"$" + name;
     }
 
     /**
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/ProguardRules.cpp
index e89fb7c..7f4dc91 100644
--- a/tools/aapt2/ProguardRules.cpp
+++ b/tools/aapt2/ProguardRules.cpp
@@ -15,9 +15,10 @@
  */
 
 #include "ProguardRules.h"
-#include "Util.h"
 #include "XmlDom.h"
 
+#include "util/Util.h"
+
 #include <memory>
 #include <string>
 
@@ -61,11 +62,11 @@
 
 protected:
     void addClass(size_t lineNumber, const std::u16string& className) {
-        mKeepSet->addClass(mSource.line(lineNumber), className);
+        mKeepSet->addClass(Source(mSource.path, lineNumber), className);
     }
 
     void addMethod(size_t lineNumber, const std::u16string& methodName) {
-        mKeepSet->addMethod(mSource.line(lineNumber), methodName);
+        mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
     }
 
 private:
@@ -186,30 +187,36 @@
     std::u16string mPackage;
 };
 
-bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) {
+bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
     ManifestVisitor visitor(source, keepSet);
-    node->accept(&visitor);
-    return true;
+    if (res->root) {
+        res->root->accept(&visitor);
+        return true;
+    }
+    return false;
 }
 
-bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
-                          KeepSet* keepSet) {
-    switch (type) {
+bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
+    if (!res->root) {
+        return false;
+    }
+
+    switch (res->file.name.type) {
         case ResourceType::kLayout: {
             LayoutVisitor visitor(source, keepSet);
-            node->accept(&visitor);
+            res->root->accept(&visitor);
             break;
         }
 
         case ResourceType::kXml: {
             XmlResourceVisitor visitor(source, keepSet);
-            node->accept(&visitor);
+            res->root->accept(&visitor);
             break;
         }
 
         case ResourceType::kTransition: {
             TransitionVisitor visitor(source, keepSet);
-            node->accept(&visitor);
+            res->root->accept(&visitor);
             break;
         }
 
@@ -221,14 +228,14 @@
 
 bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
     for (const auto& entry : keepSet.mKeepSet) {
-        for (const SourceLine& source : entry.second) {
+        for (const Source& source : entry.second) {
             *out << "// Referenced at " << source << "\n";
         }
         *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
     }
 
     for (const auto& entry : keepSet.mKeepMethodSet) {
-        for (const SourceLine& source : entry.second) {
+        for (const Source& source : entry.second) {
             *out << "// Referenced at " << source << "\n";
         }
         *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/ProguardRules.h
index bbb3e64..be61eb9 100644
--- a/tools/aapt2/ProguardRules.h
+++ b/tools/aapt2/ProguardRules.h
@@ -19,7 +19,8 @@
 
 #include "Resource.h"
 #include "Source.h"
-#include "XmlDom.h"
+
+#include "process/IResourceTableConsumer.h"
 
 #include <map>
 #include <ostream>
@@ -31,24 +32,23 @@
 
 class KeepSet {
 public:
-    inline void addClass(const SourceLine& source, const std::u16string& className) {
+    inline void addClass(const Source& source, const std::u16string& className) {
         mKeepSet[className].insert(source);
     }
 
-    inline void addMethod(const SourceLine& source, const std::u16string& methodName) {
+    inline void addMethod(const Source& source, const std::u16string& methodName) {
         mKeepMethodSet[methodName].insert(source);
     }
 
 private:
     friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
 
-    std::map<std::u16string, std::set<SourceLine>> mKeepSet;
-    std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet;
+    std::map<std::u16string, std::set<Source>> mKeepSet;
+    std::map<std::u16string, std::set<Source>> mKeepMethodSet;
 };
 
-bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet);
-bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
-                          KeepSet* keepSet);
+bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
 
 bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
 
diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h
deleted file mode 100644
index cb9318e..0000000
--- a/tools/aapt2/Resolver.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_RESOLVER_H
-#define AAPT_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resource.h"
-#include "ResourceValues.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-/**
- * Resolves symbolic references (package:type/entry) into resource IDs/objects.
- */
-class IResolver {
-public:
-    virtual ~IResolver() {};
-
-    /**
-     * Holds the result of a resource name lookup.
-     */
-    struct Entry {
-        /**
-         * The ID of the resource. ResourceId::isValid() may
-         * return false if the resource has not been assigned
-         * an ID.
-         */
-        ResourceId id;
-
-        /**
-         * If the resource is an attribute, this will point
-         * to a valid Attribute object, or else it will be
-         * nullptr.
-         */
-        const Attribute* attr;
-    };
-
-    /**
-     * Returns a ResourceID if the name is found. The ResourceID
-     * may not be valid if the resource was not assigned an ID.
-     */
-    virtual Maybe<ResourceId> findId(const ResourceName& name) = 0;
-
-    /**
-     * Returns an Entry if the name is found. Entry::attr
-     * may be nullptr if the resource is not an attribute.
-     */
-    virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0;
-
-    /**
-     * Find a resource by ID. Resolvers may contain resources without
-     * resource IDs assigned to them.
-     */
-    virtual Maybe<ResourceName> findName(ResourceId resId) = 0;
-};
-
-} // namespace aapt
-
-#endif // AAPT_RESOLVER_H
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 287d8de..1962f58 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "Resource.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <map>
 #include <string>
@@ -28,7 +28,7 @@
         case ResourceType::kAnimator:      return u"animator";
         case ResourceType::kArray:         return u"array";
         case ResourceType::kAttr:          return u"attr";
-        case ResourceType::kAttrPrivate:   return u"attr";
+        case ResourceType::kAttrPrivate:   return u"^attr-private";
         case ResourceType::kBool:          return u"bool";
         case ResourceType::kColor:         return u"color";
         case ResourceType::kDimen:         return u"dimen";
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index fa9ac07..7ef1897 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -17,12 +17,16 @@
 #ifndef AAPT_RESOURCE_H
 #define AAPT_RESOURCE_H
 
-#include "StringPiece.h"
+#include "ConfigDescription.h"
+#include "Source.h"
+
+#include "util/StringPiece.h"
 
 #include <iomanip>
 #include <limits>
 #include <string>
 #include <tuple>
+#include <vector>
 
 namespace aapt {
 
@@ -74,10 +78,14 @@
     ResourceType type;
     std::u16string entry;
 
+    ResourceName() = default;
+    ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e);
+
     bool isValid() const;
     bool operator<(const ResourceName& rhs) const;
     bool operator==(const ResourceName& rhs) const;
     bool operator!=(const ResourceName& rhs) const;
+    std::u16string toString() const;
 };
 
 /**
@@ -125,7 +133,7 @@
     ResourceId();
     ResourceId(const ResourceId& rhs);
     ResourceId(uint32_t resId);
-    ResourceId(size_t p, size_t t, size_t e);
+    ResourceId(uint8_t p, uint8_t t, uint16_t e);
 
     bool isValid() const;
     uint8_t packageId() const;
@@ -135,6 +143,29 @@
     bool operator==(const ResourceId& rhs) const;
 };
 
+struct SourcedResourceName {
+    ResourceName name;
+    size_t line;
+
+    inline bool operator==(const SourcedResourceName& rhs) const {
+        return name == rhs.name && line == rhs.line;
+    }
+};
+
+struct ResourceFile {
+    // Name
+    ResourceName name;
+
+    // Configuration
+    ConfigDescription config;
+
+    // Source
+    Source source;
+
+    // Exported symbols
+    std::vector<SourcedResourceName> exportedSymbols;
+};
+
 //
 // ResourceId implementation.
 //
@@ -148,17 +179,7 @@
 inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
 }
 
-inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) {
-    if (p > std::numeric_limits<uint8_t>::max() ||
-            t > std::numeric_limits<uint8_t>::max() ||
-            e > std::numeric_limits<uint16_t>::max()) {
-        // This will leave the ResourceId in an invalid state.
-        return;
-    }
-
-    id = (static_cast<uint8_t>(p) << 24) |
-         (static_cast<uint8_t>(t) << 16) |
-         static_cast<uint16_t>(e);
+inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {
 }
 
 inline bool ResourceId::isValid() const {
@@ -208,6 +229,10 @@
 // ResourceName implementation.
 //
 
+inline ResourceName::ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e) :
+        package(p.toString()), type(t), entry(e.toString()) {
+}
+
 inline bool ResourceName::isValid() const {
     return !package.empty() && !entry.empty();
 }
@@ -217,6 +242,10 @@
             < std::tie(rhs.package, rhs.type, rhs.entry);
 }
 
+inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
+    return ResourceNameRef(lhs) < b;
+}
+
 inline bool ResourceName::operator==(const ResourceName& rhs) const {
     return std::tie(package, type, entry)
             == std::tie(rhs.package, rhs.type, rhs.entry);
@@ -227,6 +256,18 @@
             != std::tie(rhs.package, rhs.type, rhs.entry);
 }
 
+inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
+    return ResourceNameRef(lhs) != rhs;
+}
+
+inline std::u16string ResourceName::toString() const {
+    std::u16string result;
+    if (!package.empty()) {
+        result = package + u":";
+    }
+    return result + aapt::toString(type).toString() + u"/" + entry;
+}
+
 inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
     if (!name.package.empty()) {
         out << name.package << ":";
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 13f916b..bfef9d0 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -14,473 +14,45 @@
  * limitations under the License.
  */
 
-#include "Logger.h"
 #include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "ScopedXmlPullParser.h"
-#include "SourceXmlPullParser.h"
-#include "Util.h"
-#include "XliffXmlPullParser.h"
+#include "ValueVisitor.h"
+#include "XmlPullParser.h"
+
+#include "util/Util.h"
 
 #include <sstream>
 
 namespace aapt {
 
-void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
-                                         StringPiece16* outType, StringPiece16* outEntry) {
-    const char16_t* start = str.data();
-    const char16_t* end = start + str.size();
-    const char16_t* current = start;
-    while (current != end) {
-        if (outType->size() == 0 && *current == u'/') {
-            outType->assign(start, current - start);
-            start = current + 1;
-        } else if (outPackage->size() == 0 && *current == u':') {
-            outPackage->assign(start, current - start);
-            start = current + 1;
-        }
-        current++;
-    }
-    outEntry->assign(start, end - start);
-}
+constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
 
-bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef,
-                                       bool* outCreate, bool* outPrivate) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    if (trimmedStr.empty()) {
-        return false;
-    }
-
-    if (trimmedStr.data()[0] == u'@') {
-        size_t offset = 1;
-        *outCreate = false;
-        if (trimmedStr.data()[1] == u'+') {
-            *outCreate = true;
-            offset += 1;
-        } else if (trimmedStr.data()[1] == u'*') {
-            *outPrivate = true;
-            offset += 1;
-        }
-        StringPiece16 package;
-        StringPiece16 type;
-        StringPiece16 entry;
-        extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
-                            &package, &type, &entry);
-
-        const ResourceType* parsedType = parseResourceType(type);
-        if (!parsedType) {
-            return false;
-        }
-
-        if (*outCreate && *parsedType != ResourceType::kId) {
-            return false;
-        }
-
-        outRef->package = package;
-        outRef->type = *parsedType;
-        outRef->entry = entry;
-        return true;
-    }
-    return false;
-}
-
-bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
-                                                ResourceNameRef* outRef) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    if (trimmedStr.empty()) {
-        return false;
-    }
-
-    if (*trimmedStr.data() == u'?') {
-        StringPiece16 package;
-        StringPiece16 type;
-        StringPiece16 entry;
-        extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
-
-        if (!type.empty() && type != u"attr") {
-            return false;
-        }
-
-        outRef->package = package;
-        outRef->type = ResourceType::kAttr;
-        outRef->entry = entry;
-        return true;
-    }
-    return false;
-}
-
-/*
- * Style parent's are a bit different. We accept the following formats:
- *
- * @[package:]style/<entry>
- * ?[package:]style/<entry>
- * <package>:[style/]<entry>
- * [package:style/]<entry>
- */
-bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference,
-                                               std::string* outError) {
-    if (str.empty()) {
-        return true;
-    }
-
-    StringPiece16 name = str;
-
-    bool hasLeadingIdentifiers = false;
-    bool privateRef = false;
-
-    // Skip over these identifiers. A style's parent is a normal reference.
-    if (name.data()[0] == u'@' || name.data()[0] == u'?') {
-        hasLeadingIdentifiers = true;
-        name = name.substr(1, name.size() - 1);
-        if (name.data()[0] == u'*') {
-            privateRef = true;
-            name = name.substr(1, name.size() - 1);
-        }
-    }
-
-    ResourceNameRef ref;
-    ref.type = ResourceType::kStyle;
-
-    StringPiece16 typeStr;
-    extractResourceName(name, &ref.package, &typeStr, &ref.entry);
-    if (!typeStr.empty()) {
-        // If we have a type, make sure it is a Style.
-        const ResourceType* parsedType = parseResourceType(typeStr);
-        if (!parsedType || *parsedType != ResourceType::kStyle) {
-            std::stringstream err;
-            err << "invalid resource type '" << typeStr << "' for parent of style";
-            *outError = err.str();
-            return false;
-        }
-    } else {
-        // No type was defined, this should not have a leading identifier.
-        if (hasLeadingIdentifiers) {
-            std::stringstream err;
-            err << "invalid parent reference '" << str << "'";
-            *outError = err.str();
-            return false;
-        }
-    }
-
-    if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
-        std::stringstream err;
-        err << "invalid parent reference '" << str << "'";
-        *outError = err.str();
-        return false;
-    }
-
-    outReference->name = ref.toResourceName();
-    outReference->privateReference = privateRef;
-    return true;
-}
-
-std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
-                                                             bool* outCreate) {
-    ResourceNameRef ref;
-    bool privateRef = false;
-    if (tryParseReference(str, &ref, outCreate, &privateRef)) {
-        std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
-        value->privateReference = privateRef;
-        return value;
-    }
-
-    if (tryParseAttributeReference(str, &ref)) {
-        *outCreate = false;
-        return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) {
+    auto iter = parser->findAttribute(u"", name);
+    if (iter != parser->endAttributes()) {
+        return StringPiece16(util::trimWhitespace(iter->value));
     }
     return {};
 }
 
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    android::Res_value value = {};
-    if (trimmedStr == u"@null") {
-        // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
-        // Instead we set the data type to TYPE_REFERENCE with a value of 0.
-        value.dataType = android::Res_value::TYPE_REFERENCE;
-    } else if (trimmedStr == u"@empty") {
-        // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
-        value.dataType = android::Res_value::TYPE_NULL;
-        value.data = android::Res_value::DATA_NULL_EMPTY;
-    } else {
-        return {};
-    }
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr,
-                                                                    const StringPiece16& str) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    for (const auto& entry : enumAttr.symbols) {
-        // Enum symbols are stored as @package:id/symbol resources,
-        // so we need to match against the 'entry' part of the identifier.
-        const ResourceName& enumSymbolResourceName = entry.symbol.name;
-        if (trimmedStr == enumSymbolResourceName.entry) {
-            android::Res_value value = {};
-            value.dataType = android::Res_value::TYPE_INT_DEC;
-            value.data = entry.value;
-            return util::make_unique<BinaryPrimitive>(value);
+static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser,
+                                                  const StringPiece16& name) {
+    auto iter = parser->findAttribute(u"", name);
+    if (iter != parser->endAttributes()) {
+        StringPiece16 trimmed = util::trimWhitespace(iter->value);
+        if (!trimmed.empty()) {
+            return trimmed;
         }
     }
     return {};
 }
 
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr,
-                                                                    const StringPiece16& str) {
-    android::Res_value flags = {};
-    flags.dataType = android::Res_value::TYPE_INT_DEC;
-
-    for (StringPiece16 part : util::tokenize(str, u'|')) {
-        StringPiece16 trimmedPart = util::trimWhitespace(part);
-
-        bool flagSet = false;
-        for (const auto& entry : flagAttr.symbols) {
-            // Flag symbols are stored as @package:id/symbol resources,
-            // so we need to match against the 'entry' part of the identifier.
-            const ResourceName& flagSymbolResourceName = entry.symbol.name;
-            if (trimmedPart == flagSymbolResourceName.entry) {
-                flags.data |= entry.value;
-                flagSet = true;
-                break;
-            }
-        }
-
-        if (!flagSet) {
-            return {};
-        }
-    }
-    return util::make_unique<BinaryPrimitive>(flags);
-}
-
-static uint32_t parseHex(char16_t c, bool* outError) {
-   if (c >= u'0' && c <= u'9') {
-        return c - u'0';
-    } else if (c >= u'a' && c <= u'f') {
-        return c - u'a' + 0xa;
-    } else if (c >= u'A' && c <= u'F') {
-        return c - u'A' + 0xa;
-    } else {
-        *outError = true;
-        return 0xffffffffu;
-    }
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseColor(const StringPiece16& str) {
-    StringPiece16 colorStr(util::trimWhitespace(str));
-    const char16_t* start = colorStr.data();
-    const size_t len = colorStr.size();
-    if (len == 0 || start[0] != u'#') {
-        return {};
-    }
-
-    android::Res_value value = {};
-    bool error = false;
-    if (len == 4) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
-        value.data = 0xff000000u;
-        value.data |= parseHex(start[1], &error) << 20;
-        value.data |= parseHex(start[1], &error) << 16;
-        value.data |= parseHex(start[2], &error) << 12;
-        value.data |= parseHex(start[2], &error) << 8;
-        value.data |= parseHex(start[3], &error) << 4;
-        value.data |= parseHex(start[3], &error);
-    } else if (len == 5) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
-        value.data |= parseHex(start[1], &error) << 28;
-        value.data |= parseHex(start[1], &error) << 24;
-        value.data |= parseHex(start[2], &error) << 20;
-        value.data |= parseHex(start[2], &error) << 16;
-        value.data |= parseHex(start[3], &error) << 12;
-        value.data |= parseHex(start[3], &error) << 8;
-        value.data |= parseHex(start[4], &error) << 4;
-        value.data |= parseHex(start[4], &error);
-    } else if (len == 7) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
-        value.data = 0xff000000u;
-        value.data |= parseHex(start[1], &error) << 20;
-        value.data |= parseHex(start[2], &error) << 16;
-        value.data |= parseHex(start[3], &error) << 12;
-        value.data |= parseHex(start[4], &error) << 8;
-        value.data |= parseHex(start[5], &error) << 4;
-        value.data |= parseHex(start[6], &error);
-    } else if (len == 9) {
-        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
-        value.data |= parseHex(start[1], &error) << 28;
-        value.data |= parseHex(start[2], &error) << 24;
-        value.data |= parseHex(start[3], &error) << 20;
-        value.data |= parseHex(start[4], &error) << 16;
-        value.data |= parseHex(start[5], &error) << 12;
-        value.data |= parseHex(start[6], &error) << 8;
-        value.data |= parseHex(start[7], &error) << 4;
-        value.data |= parseHex(start[8], &error);
-    } else {
-        return {};
-    }
-    return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece16& str) {
-    StringPiece16 trimmedStr(util::trimWhitespace(str));
-    uint32_t data = 0;
-    if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
-        data = 0xffffffffu;
-    } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
-        return {};
-    }
-    android::Res_value value = {};
-    value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
-    value.data = data;
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) {
-    android::Res_value value;
-    if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
-        return {};
-    }
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) {
-    android::Res_value value;
-    if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
-        return {};
-    }
-    return util::make_unique<BinaryPrimitive>(value);
-}
-
-uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) {
-    switch (type) {
-        case android::Res_value::TYPE_NULL:
-        case android::Res_value::TYPE_REFERENCE:
-        case android::Res_value::TYPE_ATTRIBUTE:
-        case android::Res_value::TYPE_DYNAMIC_REFERENCE:
-            return android::ResTable_map::TYPE_REFERENCE;
-
-        case android::Res_value::TYPE_STRING:
-            return android::ResTable_map::TYPE_STRING;
-
-        case android::Res_value::TYPE_FLOAT:
-            return android::ResTable_map::TYPE_FLOAT;
-
-        case android::Res_value::TYPE_DIMENSION:
-            return android::ResTable_map::TYPE_DIMENSION;
-
-        case android::Res_value::TYPE_FRACTION:
-            return android::ResTable_map::TYPE_FRACTION;
-
-        case android::Res_value::TYPE_INT_DEC:
-        case android::Res_value::TYPE_INT_HEX:
-            return android::ResTable_map::TYPE_INTEGER |
-                    android::ResTable_map::TYPE_ENUM |
-                    android::ResTable_map::TYPE_FLAGS;
-
-        case android::Res_value::TYPE_INT_BOOLEAN:
-            return android::ResTable_map::TYPE_BOOLEAN;
-
-        case android::Res_value::TYPE_INT_COLOR_ARGB8:
-        case android::Res_value::TYPE_INT_COLOR_RGB8:
-        case android::Res_value::TYPE_INT_COLOR_ARGB4:
-        case android::Res_value::TYPE_INT_COLOR_RGB4:
-            return android::ResTable_map::TYPE_COLOR;
-
-        default:
-            return 0;
-    };
-}
-
-std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
-        const StringPiece16& value, uint32_t typeMask,
-        std::function<void(const ResourceName&)> onCreateReference) {
-    std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
-    if (nullOrEmpty) {
-        return std::move(nullOrEmpty);
-    }
-
-    bool create = false;
-    std::unique_ptr<Reference> reference = tryParseReference(value, &create);
-    if (reference) {
-        if (create && onCreateReference) {
-            onCreateReference(reference->name);
-        }
-        return std::move(reference);
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_COLOR) {
-        // Try parsing this as a color.
-        std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
-        if (color) {
-            return std::move(color);
-        }
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
-        // Try parsing this as a boolean.
-        std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
-        if (boolean) {
-            return std::move(boolean);
-        }
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
-        // Try parsing this as an integer.
-        std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
-        if (integer) {
-            return std::move(integer);
-        }
-    }
-
-    const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
-            android::ResTable_map::TYPE_DIMENSION |
-            android::ResTable_map::TYPE_FRACTION;
-    if (typeMask & floatMask) {
-        // Try parsing this as a float.
-        std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
-        if (floatingPoint) {
-            if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
-                return std::move(floatingPoint);
-            }
-        }
-    }
-    return {};
-}
-
-/**
- * We successively try to parse the string as a resource type that the Attribute
- * allows.
- */
-std::unique_ptr<Item> ResourceParser::parseItemForAttribute(
-        const StringPiece16& str, const Attribute& attr,
-        std::function<void(const ResourceName&)> onCreateReference) {
-    const uint32_t typeMask = attr.typeMask;
-    std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
-    if (value) {
-        return value;
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_ENUM) {
-        // Try parsing this as an enum.
-        std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
-        if (enumValue) {
-            return std::move(enumValue);
-        }
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_FLAGS) {
-        // Try parsing this as a flag.
-        std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
-        if (flagValue) {
-            return std::move(flagValue);
-        }
-    }
-    return {};
-}
-
-ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
+ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
                                const ConfigDescription& config,
-                               const std::shared_ptr<XmlPullParser>& parser) :
-        mTable(table), mSource(source), mConfig(config), mLogger(source),
-        mParser(std::make_shared<XliffXmlPullParser>(parser)) {
+                               const ResourceParserOptions& options) :
+        mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {
 }
 
 /**
@@ -497,6 +69,11 @@
     while (XmlPullParser::isGoodEvent(parser->next())) {
         const XmlPullParser::Event event = parser->getEvent();
         if (event == XmlPullParser::Event::kEndElement) {
+            if (!parser->getElementNamespace().empty()) {
+                // We already warned and skipped the start element, so just skip here too
+                continue;
+            }
+
             depth--;
             if (depth == 0) {
                 break;
@@ -512,15 +89,16 @@
             builder.append(parser->getText());
 
         } else if (event == XmlPullParser::Event::kStartElement) {
-            if (parser->getElementNamespace().size() > 0) {
-                mLogger.warn(parser->getLineNumber())
-                        << "skipping element '"
-                        << parser->getElementName()
-                        << "' with unknown namespace '"
-                        << parser->getElementNamespace()
-                        << "'."
-                        << std::endl;
-                XmlPullParser::skipCurrentElement(parser);
+            if (!parser->getElementNamespace().empty()) {
+                if (parser->getElementNamespace() != sXliffNamespaceUri) {
+                    // Only warn if this isn't an xliff namespace.
+                    mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                                << "skipping element '"
+                                << parser->getElementName()
+                                << "' with unknown namespace '"
+                                << parser->getElementNamespace()
+                                << "'");
+                }
                 continue;
             }
             depth++;
@@ -536,11 +114,8 @@
             }
 
             if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
-                mLogger.error(parser->getLineNumber())
-                        << "style string '"
-                        << builder.str()
-                        << "' is too long."
-                        << std::endl;
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "style string '" << builder.str() << "' is too long");
                 return false;
             }
             spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
@@ -548,11 +123,7 @@
         } else if (event == XmlPullParser::Event::kComment) {
             // Skip
         } else {
-            mLogger.warn(parser->getLineNumber())
-                    << "unknown event "
-                    << event
-                    << "."
-                    << std::endl;
+            assert(false);
         }
     }
     assert(spanStack.empty() && "spans haven't been fully processed");
@@ -561,40 +132,95 @@
     return true;
 }
 
-bool ResourceParser::parse() {
-    while (XmlPullParser::isGoodEvent(mParser->next())) {
-        if (mParser->getEvent() != XmlPullParser::Event::kStartElement) {
+bool ResourceParser::parse(XmlPullParser* parser) {
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip comments and text.
             continue;
         }
 
-        ScopedXmlPullParser parser(mParser.get());
-        if (!parser.getElementNamespace().empty() ||
-                parser.getElementName() != u"resources") {
-            mLogger.error(parser.getLineNumber())
-                    << "root element must be <resources> in the global namespace."
-                    << std::endl;
+        if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "root element must be <resources>");
             return false;
         }
 
-        if (!parseResources(&parser)) {
-            return false;
-        }
-    }
+        error |= !parseResources(parser);
+        break;
+    };
 
-    if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) {
-        mLogger.error(mParser->getLineNumber())
-                << mParser->getLastError()
-                << std::endl;
+    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+        mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                     << "xml parser error: " << parser->getLastError());
         return false;
     }
-    return true;
+    return !error;
+}
+
+static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) {
+    assert(parser->getEvent() == XmlPullParser::Event::kStartElement);
+
+    if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) {
+        if (!productToMatch) {
+            if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
+                // We didn't specify a product and this is not a default product, so skip.
+                return true;
+            }
+        } else {
+            if (productToMatch && maybeProduct.value() != productToMatch.value()) {
+                // We specified a product, but they don't match.
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+/**
+ * A parsed resource ready to be added to the ResourceTable.
+ */
+struct ParsedResource {
+    ResourceName name;
+    Source source;
+    ResourceId id;
+    SymbolState symbolState = SymbolState::kUndefined;
+    std::unique_ptr<Value> value;
+    std::list<ParsedResource> childResources;
+};
+
+// Recursively adds resources to the ResourceTable.
+static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
+                                IDiagnostics* diag, ParsedResource* res) {
+    if (res->symbolState != SymbolState::kUndefined) {
+        if (!table->setSymbolState(res->name, res->id, res->source, res->symbolState, diag)) {
+            return false;
+        }
+    }
+
+    if (!res->value) {
+        return true;
+    }
+
+    if (!table->addResource(res->name, res->id, config, res->source, std::move(res->value), diag)) {
+        return false;
+    }
+
+    bool error = false;
+    for (ParsedResource& child : res->childResources) {
+        error |= !addResourcesToTable(table, config, diag, &child);
+    }
+    return !error;
 }
 
 bool ResourceParser::parseResources(XmlPullParser* parser) {
-    bool success = true;
+    std::set<ResourceName> strippedResources;
 
+    bool error = false;
     std::u16string comment;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         const XmlPullParser::Event event = parser->getEvent();
         if (event == XmlPullParser::Event::kComment) {
             comment = parser->getComment();
@@ -603,134 +229,133 @@
 
         if (event == XmlPullParser::Event::kText) {
             if (!util::trimWhitespace(parser->getText()).empty()) {
-                comment = u"";
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "plain text not allowed here");
+                error = true;
             }
             continue;
         }
 
-        if (event != XmlPullParser::Event::kStartElement) {
-            continue;
-        }
+        assert(event == XmlPullParser::Event::kStartElement);
 
-        ScopedXmlPullParser childParser(parser);
-
-        if (!childParser.getElementNamespace().empty()) {
+        if (!parser->getElementNamespace().empty()) {
             // Skip unknown namespace.
             continue;
         }
 
-        StringPiece16 name = childParser.getElementName();
-        if (name == u"skip" || name == u"eat-comment") {
+        std::u16string elementName = parser->getElementName();
+        if (elementName == u"skip" || elementName == u"eat-comment") {
+            comment = u"";
             continue;
         }
 
-        if (name == u"private-symbols") {
-            // Handle differently.
-            mLogger.note(childParser.getLineNumber())
-                    << "got a <private-symbols> tag."
-                    << std::endl;
+        Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+        if (!maybeName) {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "<" << elementName << "> tag must have a 'name' attribute");
+            error = true;
             continue;
         }
 
-        const auto endAttrIter = childParser.endAttributes();
-        auto attrIter = childParser.findAttribute(u"", u"name");
-        if (attrIter == endAttrIter || attrIter->value.empty()) {
-            mLogger.error(childParser.getLineNumber())
-                    << "<" << name << "> tag must have a 'name' attribute."
-                    << std::endl;
-            success = false;
-            continue;
-        }
+        // Check if we should skip this product.
+        const bool stripResource = shouldStripResource(parser, mOptions.product);
 
-        // Copy because our iterator will go out of scope when
-        // we parse more XML.
-        std::u16string attributeName = attrIter->value;
-
-        if (name == u"item") {
+        if (elementName == u"item") {
             // Items simply have their type encoded in the type attribute.
-            auto typeIter = childParser.findAttribute(u"", u"type");
-            if (typeIter == endAttrIter || typeIter->value.empty()) {
-                mLogger.error(childParser.getLineNumber())
-                        << "<item> must have a 'type' attribute."
-                        << std::endl;
-                success = false;
+            if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
+                elementName = maybeType.value().toString();
+            } else {
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "<item> must have a 'type' attribute");
+                error = true;
                 continue;
             }
-            name = typeIter->value;
         }
 
-        if (name == u"id") {
-            success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName },
-                                           {}, mSource.line(childParser.getLineNumber()),
-                                           util::make_unique<Id>());
-        } else if (name == u"string") {
-            success &= parseString(&childParser,
-                                   ResourceNameRef{ {}, ResourceType::kString, attributeName });
-        } else if (name == u"color") {
-            success &= parseColor(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kColor, attributeName });
-        } else if (name == u"drawable") {
-            success &= parseColor(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kDrawable, attributeName });
-        } else if (name == u"bool") {
-            success &= parsePrimitive(&childParser,
-                                      ResourceNameRef{ {}, ResourceType::kBool, attributeName });
-        } else if (name == u"integer") {
-            success &= parsePrimitive(
-                    &childParser,
-                    ResourceNameRef{ {}, ResourceType::kInteger, attributeName });
-        } else if (name == u"dimen") {
-            success &= parsePrimitive(&childParser,
-                                      ResourceNameRef{ {}, ResourceType::kDimen, attributeName });
-        } else if (name == u"fraction") {
-//          success &= parsePrimitive(
-//                  &childParser,
-//                  ResourceNameRef{ {}, ResourceType::kFraction, attributeName });
-        } else if (name == u"style") {
-            success &= parseStyle(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kStyle, attributeName });
-        } else if (name == u"plurals") {
-            success &= parsePlural(&childParser,
-                                   ResourceNameRef{ {}, ResourceType::kPlurals, attributeName });
-        } else if (name == u"array") {
-            success &= parseArray(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kArray, attributeName },
-                                  android::ResTable_map::TYPE_ANY);
-        } else if (name == u"string-array") {
-            success &= parseArray(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kArray, attributeName },
-                                  android::ResTable_map::TYPE_STRING);
-        } else if (name == u"integer-array") {
-            success &= parseArray(&childParser,
-                                  ResourceNameRef{ {}, ResourceType::kArray, attributeName },
-                                  android::ResTable_map::TYPE_INTEGER);
-        } else if (name == u"public") {
-            success &= parsePublic(&childParser, attributeName);
-        } else if (name == u"declare-styleable") {
-            success &= parseDeclareStyleable(
-                    &childParser,
-                    ResourceNameRef{ {}, ResourceType::kStyleable, attributeName });
-        } else if (name == u"attr") {
-            success &= parseAttr(&childParser,
-                                 ResourceNameRef{ {}, ResourceType::kAttr, attributeName });
-        } else if (name == u"bag") {
-        } else if (name == u"public-padding") {
-        } else if (name == u"java-symbol") {
-        } else if (name == u"add-resource") {
-       }
+        ParsedResource parsedResource;
+        parsedResource.name.entry = maybeName.value().toString();
+        parsedResource.source = mSource.withLine(parser->getLineNumber());
+
+        bool result = true;
+        if (elementName == u"id") {
+            parsedResource.name.type = ResourceType::kId;
+            parsedResource.value = util::make_unique<Id>();
+        } else if (elementName == u"string") {
+            parsedResource.name.type = ResourceType::kString;
+            result = parseString(parser, &parsedResource);
+        } else if (elementName == u"color") {
+            parsedResource.name.type = ResourceType::kColor;
+            result = parseColor(parser, &parsedResource);
+        } else if (elementName == u"drawable") {
+            parsedResource.name.type = ResourceType::kDrawable;
+            result = parseColor(parser, &parsedResource);
+        } else if (elementName == u"bool") {
+            parsedResource.name.type = ResourceType::kBool;
+            result = parsePrimitive(parser, &parsedResource);
+        } else if (elementName == u"integer") {
+            parsedResource.name.type = ResourceType::kInteger;
+            result = parsePrimitive(parser, &parsedResource);
+        } else if (elementName == u"dimen") {
+            parsedResource.name.type = ResourceType::kDimen;
+            result = parsePrimitive(parser, &parsedResource);
+        } else if (elementName == u"style") {
+            parsedResource.name.type = ResourceType::kStyle;
+            result = parseStyle(parser, &parsedResource);
+        } else if (elementName == u"plurals") {
+            parsedResource.name.type = ResourceType::kPlurals;
+            result = parsePlural(parser, &parsedResource);
+        } else if (elementName == u"array") {
+            parsedResource.name.type = ResourceType::kArray;
+            result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_ANY);
+        } else if (elementName == u"string-array") {
+            parsedResource.name.type = ResourceType::kArray;
+            result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING);
+        } else if (elementName == u"integer-array") {
+            parsedResource.name.type = ResourceType::kIntegerArray;
+            result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER);
+        } else if (elementName == u"declare-styleable") {
+            parsedResource.name.type = ResourceType::kStyleable;
+            result = parseDeclareStyleable(parser, &parsedResource);
+        } else if (elementName == u"attr") {
+            parsedResource.name.type = ResourceType::kAttr;
+            result = parseAttr(parser, &parsedResource);
+        } else if (elementName == u"public") {
+            result = parsePublic(parser, &parsedResource);
+        } else if (elementName == u"java-symbol" || elementName == u"symbol") {
+            result = parseSymbol(parser, &parsedResource);
+        } else {
+            mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                        << "unknown resource type '" << elementName << "'");
+        }
+
+        if (result) {
+            // We successfully parsed the resource.
+
+            if (stripResource) {
+                // Record that we stripped out this resource name.
+                // We will check that at least one variant of this resource was included.
+                strippedResources.insert(parsedResource.name);
+            } else {
+                error |= !addResourcesToTable(mTable, mConfig, mDiag, &parsedResource);
+            }
+        } else {
+            error = true;
+        }
     }
 
-    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
-        mLogger.error(parser->getLineNumber())
-                << parser->getLastError()
-                << std::endl;
-        return false;
+    // Check that we included at least one variant of each stripped resource.
+    for (const ResourceName& strippedResource : strippedResources) {
+        if (!mTable->findResource(strippedResource)) {
+            // Failed to find the resource.
+            mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' "
+                         "was filtered out but no product variant remains");
+            error = true;
+        }
     }
-    return success;
+
+    return !error;
 }
 
-
-
 enum {
     kAllowRawString = true,
     kNoRawString = false
@@ -753,34 +378,29 @@
         return {};
     }
 
-    StringPool& pool = mTable->getValueStringPool();
-
     if (!styleString.spans.empty()) {
         // This can only be a StyledString.
         return util::make_unique<StyledString>(
-                pool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
+                mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
     }
 
     auto onCreateReference = [&](const ResourceName& name) {
         // name.package can be empty here, as it will assume the package name of the table.
-        mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>());
+        mTable->addResource(name, {}, mSource.withLine(beginXmlLine), util::make_unique<Id>(),
+                            mDiag);
     };
 
     // Process the raw value.
-    std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask,
-                                                                onCreateReference);
+    std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask,
+                                                                               onCreateReference);
     if (processedItem) {
         // Fix up the reference.
-        visitFunc<Reference>(*processedItem, [&](Reference& ref) {
-            if (!ref.name.package.empty()) {
-                // The package name was set, so lookup its alias.
-                parser->applyPackageAlias(&ref.name.package, mTable->getPackage());
-            } else {
-                // The package name was left empty, so it assumes the default package
-                // without alias lookup.
-                ref.name.package = mTable->getPackage();
+        if (Reference* ref = valueCast<Reference>(processedItem.get())) {
+            if (Maybe<ResourceName> transformedName =
+                    parser->transformPackage(ref->name.value(), u"")) {
+                ref->name = std::move(transformedName);
             }
-        });
+        }
         return processedItem;
     }
 
@@ -788,142 +408,133 @@
     if (typeMask & android::ResTable_map::TYPE_STRING) {
         // Use the trimmed, escaped string.
         return util::make_unique<String>(
-                pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
+                mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
     }
 
     // We can't parse this so return a RawString if we are allowed.
     if (allowRawValue) {
         return util::make_unique<RawString>(
-                pool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
+                mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
     }
     return {};
 }
 
-bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
 
-    // Mark the string as untranslateable if needed.
-    const auto endAttrIter = parser->endAttributes();
-    auto attrIter = parser->findAttribute(u"", u"untranslateable");
-    // bool untranslateable = attrIter != endAttrIter;
-    // TODO(adamlesinski): Do something with this (mark the string).
+    // TODO(adamlesinski): Read "untranslateable" attribute.
 
-    // Deal with the product.
-    attrIter = parser->findAttribute(u"", u"product");
-    if (attrIter != endAttrIter) {
-        if (attrIter->value != u"default" && attrIter->value != u"phone") {
-            // TODO(adamlesinski): Match products.
-            return true;
-        }
-    }
-
-    std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING,
-                                                   kNoRawString);
-    if (!processedItem) {
-        mLogger.error(source.line)
-                << "not a valid string."
-                << std::endl;
+    outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
+    if (!outResource->value) {
+        mDiag->error(DiagMessage(source) << "not a valid string");
         return false;
     }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(processedItem));
+    return true;
 }
 
-bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
 
-    std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
-    if (!item) {
-        mLogger.error(source.line) << "invalid color." << std::endl;
+    outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
+    if (!outResource->value) {
+        mDiag->error(DiagMessage(source) << "invalid color");
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(item));
+    return true;
 }
 
-bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
 
     uint32_t typeMask = 0;
-    switch (resourceName.type) {
-        case ResourceType::kInteger:
-            typeMask |= android::ResTable_map::TYPE_INTEGER;
-            break;
+    switch (outResource->name.type) {
+    case ResourceType::kInteger:
+        typeMask |= android::ResTable_map::TYPE_INTEGER;
+        break;
 
-        case ResourceType::kDimen:
-            typeMask |= android::ResTable_map::TYPE_DIMENSION
-                     | android::ResTable_map::TYPE_FLOAT
-                     | android::ResTable_map::TYPE_FRACTION;
-            break;
+    case ResourceType::kDimen:
+        typeMask |= android::ResTable_map::TYPE_DIMENSION
+                  | android::ResTable_map::TYPE_FLOAT
+                  | android::ResTable_map::TYPE_FRACTION;
+        break;
 
-        case ResourceType::kBool:
-            typeMask |= android::ResTable_map::TYPE_BOOLEAN;
-            break;
+    case ResourceType::kBool:
+        typeMask |= android::ResTable_map::TYPE_BOOLEAN;
+        break;
 
-        default:
-            assert(false);
-            break;
+    default:
+        assert(false);
+        break;
     }
 
-    std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
-    if (!item) {
-        mLogger.error(source.line)
-                << "invalid "
-                << resourceName.type
-                << "."
-                << std::endl;
+    outResource->value = parseXml(parser, typeMask, kNoRawString);
+    if (!outResource->value) {
+        mDiag->error(DiagMessage(source) << "invalid " << outResource->name.type);
         return false;
     }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(item));
+    return true;
 }
 
-bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
 
-    const auto endAttrIter = parser->endAttributes();
-    const auto typeAttrIter = parser->findAttribute(u"", u"type");
-    if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) {
-        mLogger.error(source.line)
-                << "<public> must have a 'type' attribute."
-                << std::endl;
+    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    if (!maybeType) {
+        mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute");
         return false;
     }
 
-    const ResourceType* parsedType = parseResourceType(typeAttrIter->value);
+    const ResourceType* parsedType = parseResourceType(maybeType.value());
     if (!parsedType) {
-        mLogger.error(source.line)
-                << "invalid resource type '"
-                << typeAttrIter->value
-                << "' in <public>."
-                << std::endl;
+        mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
+                     << "' in <public>");
         return false;
     }
 
-    ResourceNameRef resourceName { {}, *parsedType, name };
-    ResourceId resourceId;
+    outResource->name.type = *parsedType;
 
-    const auto idAttrIter = parser->findAttribute(u"", u"id");
-    if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) {
+    if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
         android::Res_value val;
-        bool result = android::ResTable::stringToInt(idAttrIter->value.data(),
-                                                     idAttrIter->value.size(), &val);
-        resourceId.id = val.data;
+        bool result = android::ResTable::stringToInt(maybeId.value().data(),
+                                                     maybeId.value().size(), &val);
+        ResourceId resourceId(val.data);
         if (!result || !resourceId.isValid()) {
-            mLogger.error(source.line)
-                    << "invalid resource ID '"
-                    << idAttrIter->value
-                    << "' in <public>."
-                    << std::endl;
+            mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value()
+                         << "' in <public>");
             return false;
         }
+        outResource->id = resourceId;
     }
 
     if (*parsedType == ResourceType::kId) {
         // An ID marked as public is also the definition of an ID.
-        mTable->addResource(resourceName, {}, source, util::make_unique<Id>());
+        outResource->value = util::make_unique<Id>();
     }
 
-    return mTable->markPublic(resourceName, resourceId, source);
+    outResource->symbolState = SymbolState::kPublic;
+    return true;
+}
+
+bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+
+    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    if (!maybeType) {
+        mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
+                     "'type' attribute");
+        return false;
+    }
+
+    const ResourceType* parsedType = parseResourceType(maybeType.value());
+    if (!parsedType) {
+        mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
+                     << "' in <" << parser->getElementName() << ">");
+        return false;
+    }
+
+    outResource->name.type = *parsedType;
+    outResource->symbolState = SymbolState::kPrivate;
+    return true;
 }
 
 static uint32_t parseFormatType(const StringPiece16& piece) {
@@ -953,150 +564,130 @@
     return mask;
 }
 
-bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
-    ResourceName actualName = resourceName.toResourceName();
-    std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false);
-    if (!attr) {
-        return false;
-    }
-    return mTable->addResource(actualName, mConfig, source, std::move(attr));
+
+bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
+    outResource->source = mSource.withLine(parser->getLineNumber());
+    return parseAttrImpl(parser, outResource, false);
 }
 
-std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
-                                                         ResourceName* resourceName,
-                                                         bool weak) {
+bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) {
     uint32_t typeMask = 0;
 
-    const auto endAttrIter = parser->endAttributes();
-    const auto formatAttrIter = parser->findAttribute(u"", u"format");
-    if (formatAttrIter != endAttrIter) {
-        typeMask = parseFormatAttribute(formatAttrIter->value);
+    Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
+    if (maybeFormat) {
+        typeMask = parseFormatAttribute(maybeFormat.value());
         if (typeMask == 0) {
-            mLogger.error(parser->getLineNumber())
-                    << "invalid attribute format '"
-                    << formatAttrIter->value
-                    << "'."
-                    << std::endl;
-            return {};
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "invalid attribute format '" << maybeFormat.value() << "'");
+            return false;
         }
     }
 
     // If this is a declaration, the package name may be in the name. Separate these out.
     // Eg. <attr name="android:text" />
     // No format attribute is allowed.
-    if (weak && formatAttrIter == endAttrIter) {
+    if (weak && !maybeFormat) {
         StringPiece16 package, type, name;
-        extractResourceName(resourceName->entry, &package, &type, &name);
+        ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name);
         if (type.empty() && !package.empty()) {
-            resourceName->package = package.toString();
-            resourceName->entry = name.toString();
+            outResource->name.package = package.toString();
+            outResource->name.entry = name.toString();
         }
     }
 
     std::vector<Attribute::Symbol> items;
 
+    std::u16string comment;
     bool error = false;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip comments and text.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
-
-        const std::u16string& name = childParser.getElementName();
-        if (!childParser.getElementNamespace().empty()
-                || (name != u"flag" && name != u"enum")) {
-            mLogger.error(childParser.getLineNumber())
-                    << "unexpected tag <"
-                    << name
-                    << "> in <attr>."
-                    << std::endl;
-            error = true;
-            continue;
-        }
-
-        if (name == u"enum") {
-            if (typeMask & android::ResTable_map::TYPE_FLAGS) {
-                mLogger.error(childParser.getLineNumber())
-                        << "can not define an <enum>; already defined a <flag>."
-                        << std::endl;
-                error = true;
-                continue;
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) {
+            if (elementName == u"enum") {
+                if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+                    mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                                 << "can not define an <enum>; already defined a <flag>");
+                    error = true;
+                    continue;
+                }
+                typeMask |= android::ResTable_map::TYPE_ENUM;
+            } else if (elementName == u"flag") {
+                if (typeMask & android::ResTable_map::TYPE_ENUM) {
+                    mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                                 << "can not define a <flag>; already defined an <enum>");
+                    error = true;
+                    continue;
+                }
+                typeMask |= android::ResTable_map::TYPE_FLAGS;
             }
-            typeMask |= android::ResTable_map::TYPE_ENUM;
-        } else if (name == u"flag") {
-            if (typeMask & android::ResTable_map::TYPE_ENUM) {
-                mLogger.error(childParser.getLineNumber())
-                        << "can not define a <flag>; already defined an <enum>."
-                        << std::endl;
-                error = true;
-                continue;
-            }
-            typeMask |= android::ResTable_map::TYPE_FLAGS;
-        }
 
-        Attribute::Symbol item;
-        if (parseEnumOrFlagItem(&childParser, name, &item)) {
-            if (!mTable->addResource(item.symbol.name, mConfig,
-                                     mSource.line(childParser.getLineNumber()),
-                                     util::make_unique<Id>())) {
-                error = true;
+            if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
+                ParsedResource childResource;
+                childResource.name = s.value().symbol.name.value();
+                childResource.source = mSource.withLine(parser->getLineNumber());
+                childResource.value = util::make_unique<Id>();
+                outResource->childResources.push_back(std::move(childResource));
+                items.push_back(std::move(s.value()));
             } else {
-                items.push_back(std::move(item));
+                error = true;
             }
+        } else if (elementName == u"skip" || elementName == u"eat-comment") {
+            comment = u"";
+
         } else {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << ":" << elementName << ">");
             error = true;
         }
     }
 
     if (error) {
-        return {};
+        return false;
     }
 
     std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
     attr->symbols.swap(items);
     attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
-    return attr;
-}
-
-bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
-                                         Attribute::Symbol* outSymbol) {
-    const auto attrIterEnd = parser->endAttributes();
-    const auto nameAttrIter = parser->findAttribute(u"", u"name");
-    if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) {
-        mLogger.error(parser->getLineNumber())
-                << "no attribute 'name' found for tag <" << tag << ">."
-                << std::endl;
-        return false;
-    }
-
-    const auto valueAttrIter = parser->findAttribute(u"", u"value");
-    if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) {
-        mLogger.error(parser->getLineNumber())
-                << "no attribute 'value' found for tag <" << tag << ">."
-                << std::endl;
-        return false;
-    }
-
-    android::Res_value val;
-    if (!android::ResTable::stringToInt(valueAttrIter->value.data(),
-                                        valueAttrIter->value.size(), &val)) {
-        mLogger.error(parser->getLineNumber())
-                << "invalid value '"
-                << valueAttrIter->value
-                << "' for <" << tag << ">; must be an integer."
-                << std::endl;
-        return false;
-    }
-
-    outSymbol->symbol.name = ResourceName {
-            mTable->getPackage(), ResourceType::kId, nameAttrIter->value };
-    outSymbol->value = val.data;
+    outResource->value = std::move(attr);
     return true;
 }
 
-static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) {
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
+                                                             const StringPiece16& tag) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+
+    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    if (!maybeName) {
+        mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
+        return {};
+    }
+
+    Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value");
+    if (!maybeValue) {
+        mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
+        return {};
+    }
+
+    android::Res_value val;
+    if (!android::ResTable::stringToInt(maybeValue.value().data(),
+                                        maybeValue.value().size(), &val)) {
+        mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
+                     << "' for <" << tag << ">; must be an integer");
+        return {};
+    }
+
+    return Attribute::Symbol{
+        Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
+                val.data };
+}
+
+static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
     str = util::trimWhitespace(str);
     const char16_t* const start = str.data();
     const char16_t* const end = start + str.size();
@@ -1113,289 +704,279 @@
         p++;
     }
 
-    outName->package = package.toString();
-    outName->type = ResourceType::kAttr;
-    if (name.size() == 0) {
-        outName->entry = str.toString();
-    } else {
-        outName->entry = name.toString();
-    }
-    return true;
+    return ResourceName{ package.toString(), ResourceType::kAttr,
+        name.empty() ? str.toString() : name.toString() };
 }
 
-bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
-    const auto endAttrIter = parser->endAttributes();
-    const auto nameAttrIter = parser->findAttribute(u"", u"name");
-    if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) {
-        mLogger.error(parser->getLineNumber())
-                << "<item> must have a 'name' attribute."
-                << std::endl;
+
+bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+
+    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    if (!maybeName) {
+        mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
         return false;
     }
 
-    ResourceName key;
-    if (!parseXmlAttributeName(nameAttrIter->value, &key)) {
-        mLogger.error(parser->getLineNumber())
-                << "invalid attribute name '"
-                << nameAttrIter->value
-                << "'."
-                << std::endl;
+    Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value());
+    if (!maybeKey) {
+        mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
         return false;
     }
 
-    if (!key.package.empty()) {
-        // We have a package name set, so lookup its alias.
-        parser->applyPackageAlias(&key.package, mTable->getPackage());
-    } else {
-        // The package name was omitted, so use the default package name with
-        // no alias lookup.
-        key.package = mTable->getPackage();
+    if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) {
+        maybeKey = std::move(transformedName);
     }
 
     std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
     if (!value) {
+        mDiag->error(DiagMessage(source) << "could not parse style item");
         return false;
     }
 
-    style.entries.push_back(Style::Entry{ Reference(key), std::move(value) });
+    style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) });
     return true;
 }
 
-bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Style> style = util::make_unique<Style>();
 
-    const auto endAttrIter = parser->endAttributes();
-    const auto parentAttrIter = parser->findAttribute(u"", u"parent");
-    if (parentAttrIter != endAttrIter) {
-        std::string errStr;
-        if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) {
-            mLogger.error(source.line) << errStr << "." << std::endl;
-            return false;
+    Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent");
+    if (maybeParent) {
+        // If the parent is empty, we don't have a parent, but we also don't infer either.
+        if (!maybeParent.value().empty()) {
+            std::string errStr;
+            style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr);
+            if (!style->parent) {
+                mDiag->error(DiagMessage(source) << errStr);
+                return false;
+            }
+
+            if (Maybe<ResourceName> transformedName =
+                    parser->transformPackage(style->parent.value().name.value(), u"")) {
+                style->parent.value().name = std::move(transformedName);
+            }
         }
 
-        if (!style->parent.name.package.empty()) {
-            // Try to interpret the package name as an alias. These take precedence.
-            parser->applyPackageAlias(&style->parent.name.package, mTable->getPackage());
-        } else {
-            // If no package is specified, this can not be an alias and is the local package.
-            style->parent.name.package = mTable->getPackage();
-        }
     } else {
         // No parent was specified, so try inferring it from the style name.
-        std::u16string styleName = resourceName.entry.toString();
+        std::u16string styleName = outResource->name.entry;
         size_t pos = styleName.find_last_of(u'.');
         if (pos != std::string::npos) {
             style->parentInferred = true;
-            style->parent.name.package = mTable->getPackage();
-            style->parent.name.type = ResourceType::kStyle;
-            style->parent.name.entry = styleName.substr(0, pos);
+            style->parent = Reference(
+                    ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
         }
     }
 
-    bool success = true;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
-            continue;
-        }
-
-        ScopedXmlPullParser childParser(parser);
-        const std::u16string& name = childParser.getElementName();
-        if (name == u"item") {
-            success &= parseUntypedItem(&childParser, *style);
-        } else {
-            mLogger.error(childParser.getLineNumber())
-                    << "unexpected tag <"
-                    << name
-                    << "> in <style> resource."
-                    << std::endl;
-            success = false;
-        }
-    }
-
-    if (!success) {
-        return false;
-    }
-
-    return mTable->addResource(resourceName, mConfig, source, std::move(style));
-}
-
-bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
-                                uint32_t typeMask) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
-    std::unique_ptr<Array> array = util::make_unique<Array>();
-
     bool error = false;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    std::u16string comment;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip text and comments.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace == u"" && elementName == u"item") {
+            error |= !parseStyleItem(parser, style.get());
 
-        if (childParser.getElementName() != u"item") {
-            mLogger.error(childParser.getLineNumber())
-                    << "unexpected tag <"
-                    << childParser.getElementName()
-                    << "> in <array> resource."
-                    << std::endl;
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
+
+        } else {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << ":" << elementName << ">");
             error = true;
-            continue;
         }
-
-        std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString);
-        if (!item) {
-            error = true;
-            continue;
-        }
-        array->items.emplace_back(std::move(item));
     }
 
     if (error) {
         return false;
     }
 
-    return mTable->addResource(resourceName, mConfig, source, std::move(array));
+    outResource->value = std::move(style);
+    return true;
 }
 
-bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource,
+                                uint32_t typeMask) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+    std::unique_ptr<Array> array = util::make_unique<Array>();
+
+    std::u16string comment;
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip text and comments.
+            continue;
+        }
+
+        const Source itemSource = mSource.withLine(parser->getLineNumber());
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace.empty() && elementName == u"item") {
+            std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
+            if (!item) {
+                mDiag->error(DiagMessage(itemSource) << "could not parse array item");
+                error = true;
+                continue;
+            }
+            array->items.emplace_back(std::move(item));
+
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
+
+        } else {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "unknown tag <" << elementNamespace << ":" << elementName << ">");
+            error = true;
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+
+    outResource->value = std::move(array);
+    return true;
+}
+
+bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
 
-    bool success = true;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    std::u16string comment;
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip text and comments.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace.empty() && elementName == u"item") {
+            const auto endAttrIter = parser->endAttributes();
+            auto attrIter = parser->findAttribute(u"", u"quantity");
+            if (attrIter == endAttrIter || attrIter->value.empty()) {
+                mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute "
+                             << "'quantity'");
+                error = true;
+                continue;
+            }
 
-        if (!childParser.getElementNamespace().empty() ||
-                childParser.getElementName() != u"item") {
-            success = false;
-            continue;
-        }
+            StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
+            size_t index = 0;
+            if (trimmedQuantity == u"zero") {
+                index = Plural::Zero;
+            } else if (trimmedQuantity == u"one") {
+                index = Plural::One;
+            } else if (trimmedQuantity == u"two") {
+                index = Plural::Two;
+            } else if (trimmedQuantity == u"few") {
+                index = Plural::Few;
+            } else if (trimmedQuantity == u"many") {
+                index = Plural::Many;
+            } else if (trimmedQuantity == u"other") {
+                index = Plural::Other;
+            } else {
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "<item> in <plural> has invalid value '" << trimmedQuantity
+                             << "' for attribute 'quantity'");
+                error = true;
+                continue;
+            }
 
-        const auto endAttrIter = childParser.endAttributes();
-        auto attrIter = childParser.findAttribute(u"", u"quantity");
-        if (attrIter == endAttrIter || attrIter->value.empty()) {
-            mLogger.error(childParser.getLineNumber())
-                    << "<item> in <plurals> requires attribute 'quantity'."
-                    << std::endl;
-            success = false;
-            continue;
-        }
+            if (plural->values[index]) {
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "duplicate quantity '" << trimmedQuantity << "'");
+                error = true;
+                continue;
+            }
 
-        StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
-        size_t index = 0;
-        if (trimmedQuantity == u"zero") {
-            index = Plural::Zero;
-        } else if (trimmedQuantity == u"one") {
-            index = Plural::One;
-        } else if (trimmedQuantity == u"two") {
-            index = Plural::Two;
-        } else if (trimmedQuantity == u"few") {
-            index = Plural::Few;
-        } else if (trimmedQuantity == u"many") {
-            index = Plural::Many;
-        } else if (trimmedQuantity == u"other") {
-            index = Plural::Other;
+            if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
+                                                   kNoRawString))) {
+                error = true;
+            }
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
         } else {
-            mLogger.error(childParser.getLineNumber())
-                    << "<item> in <plural> has invalid value '"
-                    << trimmedQuantity
-                    << "' for attribute 'quantity'."
-                    << std::endl;
-            success = false;
-            continue;
-        }
-
-        if (plural->values[index]) {
-            mLogger.error(childParser.getLineNumber())
-                    << "duplicate quantity '"
-                    << trimmedQuantity
-                    << "'."
-                    << std::endl;
-            success = false;
-            continue;
-        }
-
-        if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING,
-                                               kNoRawString))) {
-            success = false;
+            mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+                         << elementName << ">");
+            error = true;
         }
     }
 
-    if (!success) {
+    if (error) {
         return false;
     }
 
-    return mTable->addResource(resourceName, mConfig, source, std::move(plural));
+    outResource->value = std::move(plural);
+    return true;
 }
 
-bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
-                                           const ResourceNameRef& resourceName) {
-    const SourceLine source = mSource.line(parser->getLineNumber());
+bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
 
-    bool success = true;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
+    std::u16string comment;
+    bool error = false;
+    const size_t depth = parser->getDepth();
+    while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Ignore text and comments.
             continue;
         }
 
-        ScopedXmlPullParser childParser(parser);
-
-        const std::u16string& elementName = childParser.getElementName();
-        if (elementName == u"attr") {
-            const auto endAttrIter = childParser.endAttributes();
-            auto attrIter = childParser.findAttribute(u"", u"name");
+        const std::u16string& elementNamespace = parser->getElementNamespace();
+        const std::u16string& elementName = parser->getElementName();
+        if (elementNamespace.empty() && elementName == u"attr") {
+            const auto endAttrIter = parser->endAttributes();
+            auto attrIter = parser->findAttribute(u"", u"name");
             if (attrIter == endAttrIter || attrIter->value.empty()) {
-                mLogger.error(childParser.getLineNumber())
-                        << "<attr> tag must have a 'name' attribute."
-                        << std::endl;
-                success = false;
+                mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute");
+                error = true;
                 continue;
             }
 
-            // Copy because our iterator will be invalidated.
-            ResourceName attrResourceName = {
-                    mTable->getPackage(),
-                    ResourceType::kAttr,
-                    attrIter->value
-            };
+            ParsedResource childResource;
+            childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
+            childResource.source = mSource.withLine(parser->getLineNumber());
 
-            std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true);
-            if (!attr) {
-                success = false;
+            if (!parseAttrImpl(parser, &childResource, true)) {
+                error = true;
                 continue;
             }
 
-            styleable->entries.emplace_back(attrResourceName);
+            styleable->entries.push_back(Reference(childResource.name));
+            outResource->childResources.push_back(std::move(childResource));
 
-            // The package may have been corrected to another package. If that is so,
-            // we don't add the declaration.
-            if (attrResourceName.package == mTable->getPackage()) {
-                success &= mTable->addResource(attrResourceName, mConfig,
-                                               mSource.line(childParser.getLineNumber()),
-                                               std::move(attr));
-            }
+        } else if (elementNamespace.empty() &&
+                (elementName == u"skip" || elementName == u"eat-comment")) {
+            comment = u"";
 
-        } else if (elementName != u"eat-comment" && elementName != u"skip") {
-            mLogger.error(childParser.getLineNumber())
-                    << "<"
-                    << elementName
-                    << "> is not allowed inside <declare-styleable>."
-                    << std::endl;
-            success = false;
+        } else {
+            mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+                         << elementName << ">");
+            error = true;
         }
     }
 
-    if (!success) {
+    if (error) {
         return false;
     }
 
-    return mTable->addResource(resourceName, mConfig, source, std::move(styleable));
+    outResource->value = std::move(styleable);
+    return true;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 7618999..34c68d7 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -18,135 +18,41 @@
 #define AAPT_RESOURCE_PARSER_H
 
 #include "ConfigDescription.h"
-#include "Logger.h"
+#include "Diagnostics.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "StringPiece.h"
 #include "StringPool.h"
 #include "XmlPullParser.h"
 
-#include <istream>
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
 #include <memory>
 
 namespace aapt {
 
+struct ParsedResource;
+
+struct ResourceParserOptions {
+    /**
+     * Optional product name by which to filter resources.
+     * This is like a preprocessor definition in that we strip out resources
+     * that don't match before we compile them.
+     */
+    Maybe<std::u16string> product;
+};
+
 /*
  * Parses an XML file for resources and adds them to a ResourceTable.
  */
 class ResourceParser {
 public:
-    /*
-     * Extracts the package, type, and name from a string of the format:
-     *
-     *      [package:]type/name
-     *
-     * where the package can be empty. Validation must be performed on each
-     * individual extracted piece to verify that the pieces are valid.
-     */
-    static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
-                                    StringPiece16* outType, StringPiece16* outEntry);
-
-    /*
-     * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
-     * `outReference` set to the parsed reference.
-     *
-     * If '+' was present in the reference, `outCreate` is set to true.
-     * If '*' was present in the reference, `outPrivate` is set to true.
-     */
-    static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
-                                  bool* outCreate, bool* outPrivate);
-
-    /*
-     * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
-     * with `outReference` set to the parsed reference.
-     */
-    static bool tryParseAttributeReference(const StringPiece16& str,
-                                           ResourceNameRef* outReference);
-
-    /*
-     * Returns true if the string `str` was parsed as a valid reference to a style.
-     * The format for a style parent is slightly more flexible than a normal reference:
-     *
-     * @[package:]style/<entry> or
-     * ?[package:]style/<entry> or
-     * <package>:[style/]<entry>
-     */
-    static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference,
-                                          std::string* outError);
-
-    /*
-     * Returns a Reference object if the string was parsed as a resource or attribute reference,
-     * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
-     * the '+' was present in the string.
-     */
-    static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str,
-                                                        bool* outCreate);
-
-    /*
-     * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a color if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a boolean if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing an integer if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a floating point number
-     * (float, dimension, etc) if the string was parsed as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr,
-                                                               const StringPiece16& str);
-
-    /*
-     * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
-     * as one.
-     */
-    static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr,
-                                                               const StringPiece16& str);
-    /*
-     * Try to convert a string to an Item for the given attribute. The attribute will
-     * restrict what values the string can be converted to.
-     * The callback function onCreateReference is called when the parsed item is a
-     * reference to an ID that must be created (@+id/foo).
-     */
-    static std::unique_ptr<Item> parseItemForAttribute(
-            const StringPiece16& value, const Attribute& attr,
-            std::function<void(const ResourceName&)> onCreateReference = {});
-
-    static std::unique_ptr<Item> parseItemForAttribute(
-            const StringPiece16& value, uint32_t typeMask,
-            std::function<void(const ResourceName&)> onCreateReference = {});
-
-    static uint32_t androidTypeToAttributeTypeMask(uint16_t type);
-
-    ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
-                   const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser);
+    ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+                   const ConfigDescription& config, const ResourceParserOptions& options = {});
 
     ResourceParser(const ResourceParser&) = delete; // No copy.
 
-    bool parse();
+    bool parse(XmlPullParser* parser);
 
 private:
     /*
@@ -155,7 +61,7 @@
      * contains the escaped and whitespace trimmed text, while `outRawString`
      * contains the unescaped text. Returns true on success.
      */
-    bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\
+    bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
                            StyleString* outStyleString);
 
     /*
@@ -167,27 +73,25 @@
     std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
 
     bool parseResources(XmlPullParser* parser);
-    bool parseString(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
-    bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
-                                             ResourceName* resourceName,
-                                             bool weak);
-    bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
-                             Attribute::Symbol* outSymbol);
-    bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parseUntypedItem(XmlPullParser* parser, Style& style);
-    bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask);
-    bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    bool parseString(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
+    Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
+    bool parseStyle(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseStyleItem(XmlPullParser* parser, Style* style);
+    bool parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseArray(XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
+    bool parsePlural(XmlPullParser* parser, ParsedResource* outResource);
 
-    std::shared_ptr<ResourceTable> mTable;
+    IDiagnostics* mDiag;
+    ResourceTable* mTable;
     Source mSource;
     ConfigDescription mConfig;
-    SourceLogger mLogger;
-    std::shared_ptr<XmlPullParser> mParser;
+    ResourceParserOptions mOptions;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index a93d0ff..a7e9d39 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -16,8 +16,11 @@
 
 #include "ResourceParser.h"
 #include "ResourceTable.h"
+#include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "SourceXmlPullParser.h"
+#include "XmlPullParser.h"
+
+#include "test/Context.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -27,156 +30,43 @@
 
 constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
 
-TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) {
-    ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create,
-                                                  &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
-                                                  &create, &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create,
-                                                  &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_TRUE(create);
-    EXPECT_FALSE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, ParsePrivateReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
-    ResourceNameRef actual;
-    bool create = false;
-    bool privateRef = false;
-    EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create,
-                                                  &privateRef));
-    EXPECT_EQ(expected, actual);
-    EXPECT_FALSE(create);
-    EXPECT_TRUE(privateRef);
-}
-
-TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
-    bool create = false;
-    bool privateRef = false;
-    ResourceNameRef actual;
-    EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create,
-                                                   &privateRef));
-}
-
-TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
-    Reference ref;
-    std::string errStr;
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
-
-    EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
-    EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::stringstream input(kXmlPreamble);
+    input << "<attr name=\"foo\"/>" << std::endl;
+    ResourceTable table;
+    ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
+    XmlPullParser xmlParser(input);
+    ASSERT_FALSE(parser.parse(&xmlParser));
 }
 
 struct ResourceParserTest : public ::testing::Test {
-    virtual void SetUp() override {
-        mTable = std::make_shared<ResourceTable>();
-        mTable->setPackage(u"android");
+    ResourceTable mTable;
+    std::unique_ptr<IAaptContext> mContext;
+
+    void SetUp() override {
+        mContext = test::ContextBuilder().build();
     }
 
-    ::testing::AssertionResult testParse(const StringPiece& str) {
+    ::testing::AssertionResult testParse(const StringPiece& str,
+                                         Maybe<std::u16string> product = {}) {
         std::stringstream input(kXmlPreamble);
         input << "<resources>\n" << str << "\n</resources>" << std::endl;
-        ResourceParser parser(mTable, Source{ "test" }, {},
-                              std::make_shared<SourceXmlPullParser>(input));
-        if (parser.parse()) {
+        ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {},
+                              ResourceParserOptions{ product });
+        XmlPullParser xmlParser(input);
+        if (parser.parse(&xmlParser)) {
             return ::testing::AssertionSuccess();
         }
         return ::testing::AssertionFailure();
     }
-
-    template <typename T>
-    const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) {
-        using std::begin;
-        using std::end;
-
-        const ResourceTableType* type;
-        const ResourceEntry* entry;
-        std::tie(type, entry) = mTable->findResource(name);
-        if (!type || !entry) {
-            return nullptr;
-        }
-
-        for (const auto& configValue : entry->values) {
-            if (configValue.config == config) {
-                return dynamic_cast<const T*>(configValue.value.get());
-            }
-        }
-        return nullptr;
-    }
-
-    template <typename T>
-    const T* findResource(const ResourceNameRef& name) {
-        return findResource<T>(name, {});
-    }
-
-    std::shared_ptr<ResourceTable> mTable;
 };
 
-TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) {
-    std::stringstream input(kXmlPreamble);
-    input << "<attr name=\"foo\"/>" << std::endl;
-    ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input));
-    ASSERT_FALSE(parser.parse());
-}
-
 TEST_F(ResourceParserTest, ParseQuotedString) {
     std::string input = "<string name=\"foo\">   \"  hey there \" </string>";
     ASSERT_TRUE(testParse(input));
 
-    const String* str = findResource<String>(ResourceName{
-            u"android", ResourceType::kString, u"foo"});
+    String* str = test::getValue<String>(&mTable, u"@string/foo");
     ASSERT_NE(nullptr, str);
     EXPECT_EQ(std::u16string(u"  hey there "), *str->value);
 }
@@ -185,12 +75,22 @@
     std::string input = "<string name=\"foo\">\\?123</string>";
     ASSERT_TRUE(testParse(input));
 
-    const String* str = findResource<String>(ResourceName{
-            u"android", ResourceType::kString, u"foo" });
+    String* str = test::getValue<String>(&mTable, u"@string/foo");
     ASSERT_NE(nullptr, str);
     EXPECT_EQ(std::u16string(u"?123"), *str->value);
 }
 
+TEST_F(ResourceParserTest, IgnoreXliffTags) {
+    std::string input = "<string name=\"foo\" \n"
+                        "        xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+                        "  There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
+    ASSERT_TRUE(testParse(input));
+
+    String* str = test::getValue<String>(&mTable, u"@string/foo");
+    ASSERT_NE(nullptr, str);
+    EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value));
+}
+
 TEST_F(ResourceParserTest, ParseNull) {
     std::string input = "<integer name=\"foo\">@null</integer>";
     ASSERT_TRUE(testParse(input));
@@ -199,8 +99,7 @@
     // a non-existing value, and this causes problems in styles when trying to resolve
     // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
     // with a data value of 0.
-    const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
-            u"android", ResourceType::kInteger, u"foo" });
+    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
     ASSERT_NE(nullptr, integer);
     EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
     EXPECT_EQ(0u, integer->value.data);
@@ -210,8 +109,7 @@
     std::string input = "<integer name=\"foo\">@empty</integer>";
     ASSERT_TRUE(testParse(input));
 
-    const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{
-            u"android", ResourceType::kInteger, u"foo" });
+    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo");
     ASSERT_NE(nullptr, integer);
     EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
     EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
@@ -222,14 +120,12 @@
                         "<attr name=\"bar\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
-    EXPECT_NE(nullptr, attr);
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+    ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
 
-    attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"bar"});
-    EXPECT_NE(nullptr, attr);
+    attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
+    ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
 }
 
@@ -240,8 +136,7 @@
                         "<attr name=\"foo\" format=\"string\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
 }
@@ -255,8 +150,7 @@
                         "</declare-styleable>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
 }
@@ -269,19 +163,21 @@
                         "</attr>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* enumAttr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(enumAttr, nullptr);
     EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
     ASSERT_EQ(enumAttr->symbols.size(), 3u);
 
-    EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar");
+    AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
+    EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar");
     EXPECT_EQ(enumAttr->symbols[0].value, 0u);
 
-    EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
+    AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
+    EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat");
     EXPECT_EQ(enumAttr->symbols[1].value, 1u);
 
-    EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
+    AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
+    EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz");
     EXPECT_EQ(enumAttr->symbols[2].value, 2u);
 }
 
@@ -293,23 +189,25 @@
                         "</attr>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* flagAttr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"foo"});
+    Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo");
     ASSERT_NE(flagAttr, nullptr);
     EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
     ASSERT_EQ(flagAttr->symbols.size(), 3u);
 
-    EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar");
+    AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
+    EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar");
     EXPECT_EQ(flagAttr->symbols[0].value, 0u);
 
-    EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
+    AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
+    EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat");
     EXPECT_EQ(flagAttr->symbols[1].value, 1u);
 
-    EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
+    AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
+    EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz");
     EXPECT_EQ(flagAttr->symbols[2].value, 2u);
 
-    std::unique_ptr<BinaryPrimitive> flagValue =
-            ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
+    std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr,
+                                                                                   u"baz|bat");
     ASSERT_NE(flagValue, nullptr);
     EXPECT_EQ(flagValue->value.data, 1u | 2u);
 }
@@ -331,28 +229,32 @@
                         "</style>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo"});
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name);
-    ASSERT_EQ(style->entries.size(), 3u);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value());
+    ASSERT_EQ(3u, style->entries.size());
 
-    EXPECT_EQ(style->entries[0].key.name,
-              (ResourceName{ u"android", ResourceType::kAttr, u"bar" }));
-    EXPECT_EQ(style->entries[1].key.name,
-              (ResourceName{ u"android", ResourceType::kAttr, u"bat" }));
-    EXPECT_EQ(style->entries[2].key.name,
-              (ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value());
+
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value());
+
+    AAPT_ASSERT_TRUE(style->entries[2].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
     std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(
-            ResourceName{ u"android", ResourceType::kStyle, u"foo" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
@@ -360,10 +262,11 @@
                         "       name=\"foo\" parent=\"app:Theme\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
@@ -373,22 +276,21 @@
             "</style>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo");
     ASSERT_NE(style, nullptr);
     ASSERT_EQ(1u, style->entries.size());
-    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"),
-              style->entries[0].key.name);
+    EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
     std::string input = "<style name=\"foo.bar\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo.bar" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
     ASSERT_NE(style, nullptr);
-    EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo"));
     EXPECT_TRUE(style->parentInferred);
 }
 
@@ -396,10 +298,9 @@
     std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Style* style = findResource<Style>(ResourceName{
-            u"android", ResourceType::kStyle, u"foo.bar" });
+    Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar");
     ASSERT_NE(style, nullptr);
-    EXPECT_FALSE(style->parent.name.isValid());
+    AAPT_EXPECT_FALSE(style->parent);
     EXPECT_FALSE(style->parentInferred);
 }
 
@@ -407,7 +308,7 @@
     std::string input = "<string name=\"foo\">@+id/bar</string>";
     ASSERT_TRUE(testParse(input));
 
-    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
+    Id* id = test::getValue<Id>(&mTable, u"@id/bar");
     ASSERT_NE(id, nullptr);
 }
 
@@ -415,25 +316,33 @@
     std::string input = "<declare-styleable name=\"foo\">\n"
                         "  <attr name=\"bar\" />\n"
                         "  <attr name=\"bat\" format=\"string|reference\"/>\n"
+                        "  <attr name=\"baz\">\n"
+                        "    <enum name=\"foo\" value=\"1\"/>\n"
+                        "  </attr>\n"
                         "</declare-styleable>";
     ASSERT_TRUE(testParse(input));
 
-    const Attribute* attr = findResource<Attribute>(ResourceName{
-            u"android", ResourceType::kAttr, u"bar"});
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar");
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
 
-    attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
+    attr = test::getValue<Attribute>(&mTable, u"@attr/bat");
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
 
-    const Styleable* styleable = findResource<Styleable>(ResourceName{
-            u"android", ResourceType::kStyleable, u"foo" });
+    attr = test::getValue<Attribute>(&mTable, u"@attr/baz");
+    ASSERT_NE(attr, nullptr);
+    EXPECT_TRUE(attr->isWeak());
+    EXPECT_EQ(1u, attr->symbols.size());
+
+    EXPECT_NE(nullptr, test::getValue<Id>(&mTable, u"@id/foo"));
+
+    Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
     ASSERT_NE(styleable, nullptr);
-    ASSERT_EQ(2u, styleable->entries.size());
+    ASSERT_EQ(3u, styleable->entries.size());
 
-    EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name);
-    EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name);
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value());
+    EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
 }
 
 TEST_F(ResourceParserTest, ParseArray) {
@@ -444,14 +353,21 @@
                         "</array>";
     ASSERT_TRUE(testParse(input));
 
-    const Array* array = findResource<Array>(ResourceName{
-            u"android", ResourceType::kArray, u"foo" });
+    Array* array = test::getValue<Array>(&mTable, u"@array/foo");
     ASSERT_NE(array, nullptr);
     ASSERT_EQ(3u, array->items.size());
 
-    EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get()));
-    EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get()));
-    EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get()));
+    EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
+    EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
+    EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
+}
+
+TEST_F(ResourceParserTest, ParseStringArray) {
+    std::string input = "<string-array name=\"foo\">\n"
+                        "  <item>\"Werk\"</item>\n"
+                        "</string-array>\n";
+    ASSERT_TRUE(testParse(input));
+    EXPECT_NE(nullptr, test::getValue<Array>(&mTable, u"@array/foo"));
 }
 
 TEST_F(ResourceParserTest, ParsePlural) {
@@ -467,11 +383,11 @@
                         "<string name=\"foo\">Hi</string>";
     ASSERT_TRUE(testParse(input));
 
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = mTable->findResource(ResourceName{
-            u"android", ResourceType::kString, u"foo"});
-    ASSERT_NE(type, nullptr);
+    Maybe<ResourceTable::SearchResult> result = mTable.findResource(
+            test::parseNameOrDie(u"@string/foo"));
+    AAPT_ASSERT_TRUE(result);
+
+    ResourceEntry* entry = result.value().entry;
     ASSERT_NE(entry, nullptr);
     ASSERT_FALSE(entry->values.empty());
     EXPECT_EQ(entry->values.front().comment, u"This is a comment");
@@ -485,8 +401,28 @@
     std::string input = "<public type=\"id\" name=\"foo\"/>";
     ASSERT_TRUE(testParse(input));
 
-    const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
+    Id* id = test::getValue<Id>(&mTable, u"@id/foo");
     ASSERT_NE(nullptr, id);
 }
 
+TEST_F(ResourceParserTest, FilterProductsThatDontMatch) {
+    std::string input = "<string name=\"foo\" product=\"phone\">hi</string>\n"
+                        "<string name=\"foo\" product=\"no-sdcard\">ho</string>\n"
+                        "<string name=\"bar\" product=\"\">wee</string>\n"
+                        "<string name=\"baz\">woo</string>\n";
+    ASSERT_TRUE(testParse(input, std::u16string(u"no-sdcard")));
+
+    String* fooStr = test::getValue<String>(&mTable, u"@string/foo");
+    ASSERT_NE(nullptr, fooStr);
+    EXPECT_EQ(StringPiece16(u"ho"), *fooStr->value);
+
+    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bar"));
+    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/baz"));
+}
+
+TEST_F(ResourceParserTest, FailWhenProductFilterStripsOutAllVersionsOfResource) {
+    std::string input = "<string name=\"foo\" product=\"tablet\">hello</string>\n";
+    ASSERT_FALSE(testParse(input, std::u16string(u"phone")));
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index c93ecc7..84674e8 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -15,11 +15,11 @@
  */
 
 #include "ConfigDescription.h"
-#include "Logger.h"
 #include "NameMangler.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "ValueVisitor.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <androidfw/ResourceTypes.h>
@@ -37,65 +37,109 @@
     return lhs->type < rhs;
 }
 
-static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
+template <typename T>
+static bool lessThanStructWithName(const std::unique_ptr<T>& lhs,
+                                   const StringPiece16& rhs) {
     return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
-ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
-    // Make sure attrs always have type ID 1.
-    findOrCreateType(ResourceType::kAttr)->typeId = 1;
+ResourceTablePackage* ResourceTable::findPackage(const StringPiece16& name) {
+    const auto last = packages.end();
+    auto iter = std::lower_bound(packages.begin(), last, name,
+                                 lessThanStructWithName<ResourceTablePackage>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
+    }
+    return nullptr;
 }
 
-std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
-    auto last = mTypes.end();
-    auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
-    if (iter != last) {
-        if ((*iter)->type == type) {
-            return *iter;
+ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
+    for (auto& package : packages) {
+        if (package->id && package->id.value() == id) {
+            return package.get();
         }
     }
-    return *mTypes.emplace(iter, new ResourceTableType{ type });
+    return nullptr;
 }
 
-std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
-        std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
-    auto last = type->entries.end();
-    auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
-    if (iter != last) {
-        if (name == (*iter)->name) {
-            return *iter;
-        }
+ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, Maybe<uint8_t> id) {
+    ResourceTablePackage* package = findOrCreatePackage(name);
+    if (id && !package->id) {
+        package->id = id;
+        return package;
     }
-    return *type->entries.emplace(iter, new ResourceEntry{ name });
+
+    if (id && package->id && package->id.value() != id.value()) {
+        return nullptr;
+    }
+    return package;
 }
 
-struct IsAttributeVisitor : ConstValueVisitor {
-    bool isAttribute = false;
-
-    void visit(const Attribute&, ValueVisitorArgs&) override {
-        isAttribute = true;
+ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece16& name) {
+    const auto last = packages.end();
+    auto iter = std::lower_bound(packages.begin(), last, name,
+                                 lessThanStructWithName<ResourceTablePackage>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
     }
 
-    operator bool() {
-        return isAttribute;
+    std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>();
+    newPackage->name = name.toString();
+    return packages.emplace(iter, std::move(newPackage))->get();
+}
+
+ResourceTableType* ResourceTablePackage::findType(ResourceType type) {
+    const auto last = types.end();
+    auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+    if (iter != last && (*iter)->type == type) {
+        return iter->get();
     }
-};
+    return nullptr;
+}
+
+ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) {
+    const auto last = types.end();
+    auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+    if (iter != last && (*iter)->type == type) {
+        return iter->get();
+    }
+    return types.emplace(iter, new ResourceTableType{ type })->get();
+}
+
+ResourceEntry* ResourceTableType::findEntry(const StringPiece16& name) {
+    const auto last = entries.end();
+    auto iter = std::lower_bound(entries.begin(), last, name,
+                                 lessThanStructWithName<ResourceEntry>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
+    }
+    return nullptr;
+}
+
+ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) {
+    auto last = entries.end();
+    auto iter = std::lower_bound(entries.begin(), last, name,
+                                 lessThanStructWithName<ResourceEntry>);
+    if (iter != last && name == (*iter)->name) {
+        return iter->get();
+    }
+    return entries.emplace(iter, new ResourceEntry{ name })->get();
+}
 
 /**
  * The default handler for collisions. A return value of -1 means keep the
  * existing value, 0 means fail, and +1 means take the incoming value.
  */
-static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
-    IsAttributeVisitor existingIsAttr, incomingIsAttr;
-    existing.accept(existingIsAttr, {});
-    incoming.accept(incomingIsAttr, {});
+int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) {
+    Attribute* existingAttr = valueCast<Attribute>(existing);
+    Attribute* incomingAttr = valueCast<Attribute>(incoming);
 
-    if (!incomingIsAttr) {
-        if (incoming.isWeak()) {
+    if (!incomingAttr) {
+        if (incoming->isWeak()) {
             // We're trying to add a weak resource but a resource
             // already exists. Keep the existing.
             return -1;
-        } else if (existing.isWeak()) {
+        } else if (existing->isWeak()) {
             // Override the weak resource with the new strong resource.
             return 1;
         }
@@ -104,8 +148,8 @@
         return 0;
     }
 
-    if (!existingIsAttr) {
-        if (existing.isWeak()) {
+    if (!existingAttr) {
+        if (existing->isWeak()) {
             // The existing value is not an attribute and it is weak,
             // so take the incoming attribute value.
             return 1;
@@ -115,27 +159,27 @@
         return 0;
     }
 
+    assert(incomingAttr && existingAttr);
+
     //
     // Attribute specific handling. At this point we know both
     // values are attributes. Since we can declare and define
     // attributes all-over, we do special handling to see
     // which definition sticks.
     //
-    const Attribute& existingAttr = static_cast<const Attribute&>(existing);
-    const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
-    if (existingAttr.typeMask == incomingAttr.typeMask) {
+    if (existingAttr->typeMask == incomingAttr->typeMask) {
         // The two attributes are both DECLs, but they are plain attributes
         // with the same formats.
         // Keep the strongest one.
-        return existingAttr.isWeak() ? 1 : -1;
+        return existingAttr->isWeak() ? 1 : -1;
     }
 
-    if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
+    if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
         // Any incoming attribute is better than this.
         return 1;
     }
 
-    if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
+    if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
         // The incoming attribute may be a USE instead of a DECL.
         // Keep the existing attribute.
         return -1;
@@ -147,284 +191,240 @@
 static constexpr const char16_t* kValidNameMangledChars = u"._-$";
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                                const SourceLine& source, std::unique_ptr<Value> value) {
-    return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars);
+                                const Source& source, std::unique_ptr<Value> value,
+                                IDiagnostics* diag) {
+    return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars,
+                           diag);
 }
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
-                                const ConfigDescription& config, const SourceLine& source,
-                                std::unique_ptr<Value> value) {
-    return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars);
+                                const ConfigDescription& config, const Source& source,
+                                std::unique_ptr<Value> value, IDiagnostics* diag) {
+    return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag);
+}
+
+bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                                     const Source& source, const StringPiece16& path,
+                                     IDiagnostics* diag) {
+    return addResourceImpl(name, ResourceId{}, config, source,
+                           util::make_unique<FileReference>(stringPool.makeRef(path)),
+                           kValidNameChars, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
                                             const ConfigDescription& config,
-                                            const SourceLine& source,
-                                            std::unique_ptr<Value> value) {
+                                            const Source& source,
+                                            std::unique_ptr<Value> value,
+                                            IDiagnostics* diag) {
     return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
-                           kValidNameMangledChars);
+                           kValidNameMangledChars, diag);
+}
+
+bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+                                            const ResourceId id,
+                                            const ConfigDescription& config,
+                                            const Source& source,
+                                            std::unique_ptr<Value> value,
+                                            IDiagnostics* diag) {
+    return addResourceImpl(name, id, config, source, std::move(value),
+                           kValidNameMangledChars, diag);
 }
 
 bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
-                                    const ConfigDescription& config, const SourceLine& source,
-                                    std::unique_ptr<Value> value, const char16_t* validChars) {
-    if (!name.package.empty() && name.package != mPackage) {
-        Logger::error(source)
-                << "resource '"
-                << name
-                << "' has incompatible package. Must be '"
-                << mPackage
-                << "'."
-                << std::endl;
-        return false;
-    }
-
+                                    const ConfigDescription& config, const Source& source,
+                                    std::unique_ptr<Value> value, const char16_t* validChars,
+                                    IDiagnostics* diag) {
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
-        Logger::error(source)
-                << "resource '"
-                << name
-                << "' has invalid entry name '"
-                << name.entry
-                << "'. Invalid character '"
-                << StringPiece16(badCharIter, 1)
-                << "'."
-                << std::endl;
+        diag->error(DiagMessage(source)
+                    << "resource '"
+                    << name
+                    << "' has invalid entry name '"
+                    << name.entry
+                    << "'. Invalid character '"
+                    << StringPiece16(badCharIter, 1)
+                    << "'");
         return false;
     }
 
-    std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
-    if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
-            type->typeId != resId.typeId()) {
-        Logger::error(source)
-                << "trying to add resource '"
-                << name
-                << "' with ID "
-                << resId
-                << " but type '"
-                << type->type
-                << "' already has ID "
-                << std::hex << type->typeId << std::dec
-                << "."
-                << std::endl;
+    ResourceTablePackage* package = findOrCreatePackage(name.package);
+    if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but package '"
+                    << package->name
+                    << "' already has ID "
+                    << std::hex << (int) package->id.value() << std::dec);
         return false;
     }
 
-    std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
-    if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
-            entry->entryId != resId.entryId()) {
-        Logger::error(source)
-                << "trying to add resource '"
-                << name
-                << "' with ID "
-                << resId
-                << " but resource already has ID "
-                << ResourceId(mPackageId, type->typeId, entry->entryId)
-                << "."
-                << std::endl;
+    ResourceTableType* type = package->findOrCreateType(name.type);
+    if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but type '"
+                    << type->type
+                    << "' already has ID "
+                    << std::hex << (int) type->id.value() << std::dec);
         return false;
     }
 
-    const auto endIter = std::end(entry->values);
-    auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
+    ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+    if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but resource already has ID "
+                    << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
+        return false;
+    }
+
+    const auto endIter = entry->values.end();
+    auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs);
     if (iter == endIter || iter->config != config) {
         // This resource did not exist before, add it.
         entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
     } else {
-        int collisionResult = defaultCollisionHandler(*iter->value, *value);
+        int collisionResult = resolveValueCollision(iter->value.get(), value.get());
         if (collisionResult > 0) {
             // Take the incoming value.
             *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
         } else if (collisionResult == 0) {
-            Logger::error(source)
-                    << "duplicate value for resource '" << name << "' "
-                    << "with config '" << iter->config << "'."
-                    << std::endl;
-
-            Logger::error(iter->source)
-                    << "resource previously defined here."
-                    << std::endl;
+            diag->error(DiagMessage(source)
+                        << "duplicate value for resource '" << name << "' "
+                        << "with config '" << iter->config << "'");
+            diag->error(DiagMessage(iter->source)
+                        << "resource previously defined here");
             return false;
         }
     }
 
     if (resId.isValid()) {
-        type->typeId = resId.typeId();
-        entry->entryId = resId.entryId();
+        package->id = resId.packageId();
+        type->id = resId.typeId();
+        entry->id = resId.entryId();
     }
     return true;
 }
 
-bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
-                               const SourceLine& source) {
-    return markPublicImpl(name, resId, source, kValidNameChars);
+bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
+                                   const Source& source, SymbolState state, IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, source, state, kValidNameChars, diag);
 }
 
-bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                           const SourceLine& source) {
-    return markPublicImpl(name, resId, source, kValidNameMangledChars);
+bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                               const Source& source, SymbolState state,
+                                               IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, source, state, kValidNameMangledChars, diag);
 }
 
-bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
-                                   const SourceLine& source, const char16_t* validChars) {
-    if (!name.package.empty() && name.package != mPackage) {
-        Logger::error(source)
-                << "resource '"
-                << name
-                << "' has incompatible package. Must be '"
-                << mPackage
-                << "'."
-            << std::endl;
-        return false;
+bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
+                                       const Source& source, SymbolState state,
+                                       const char16_t* validChars, IDiagnostics* diag) {
+    if (state == SymbolState::kUndefined) {
+        // Nothing to do.
+        return true;
     }
 
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
-        Logger::error(source)
-                << "resource '"
-                << name
-                << "' has invalid entry name '"
-                << name.entry
-                << "'. Invalid character '"
-                << StringPiece16(badCharIter, 1)
-                << "'."
-                << std::endl;
+        diag->error(DiagMessage(source)
+                    << "resource '"
+                    << name
+                    << "' has invalid entry name '"
+                    << name.entry
+                    << "'. Invalid character '"
+                    << StringPiece16(badCharIter, 1)
+                    << "'");
         return false;
     }
 
-    std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
-    if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
-            type->typeId != resId.typeId()) {
-        Logger::error(source)
-                << "trying to make resource '"
-                << name
-                << "' public with ID "
-                << resId
-                << " but type '"
-                << type->type
-                << "' already has ID "
-                << std::hex << type->typeId << std::dec
-                << "."
-                << std::endl;
+    ResourceTablePackage* package = findOrCreatePackage(name.package);
+    if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but package '"
+                    << package->name
+                    << "' already has ID "
+                    << std::hex << (int) package->id.value() << std::dec);
         return false;
     }
 
-    std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
-    if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
-            entry->entryId != resId.entryId()) {
-        Logger::error(source)
-                << "trying to make resource '"
-                << name
-                << "' public with ID "
-                << resId
-                << " but resource already has ID "
-                << ResourceId(mPackageId, type->typeId, entry->entryId)
-                << "."
-                << std::endl;
+    ResourceTableType* type = package->findOrCreateType(name.type);
+    if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but type '"
+                    << type->type
+                    << "' already has ID "
+                    << std::hex << (int) type->id.value() << std::dec);
         return false;
     }
 
-    type->publicStatus.isPublic = true;
-    entry->publicStatus.isPublic = true;
-    entry->publicStatus.source = source;
+    ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+    if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+        diag->error(DiagMessage(source)
+                    << "trying to add resource '"
+                    << name
+                    << "' with ID "
+                    << resId
+                    << " but resource already has ID "
+                    << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
+        return false;
+    }
+
+    // Only mark the type state as public, it doesn't care about being private.
+    if (state == SymbolState::kPublic) {
+        type->symbolStatus.state = SymbolState::kPublic;
+    }
+
+    // Downgrading to a private symbol from a public one is not allowed.
+    if (entry->symbolStatus.state != SymbolState::kPublic) {
+        if (entry->symbolStatus.state != state) {
+            entry->symbolStatus.state = state;
+            entry->symbolStatus.source = source;
+        }
+    }
 
     if (resId.isValid()) {
-        type->typeId = resId.typeId();
-        entry->entryId = resId.entryId();
+        package->id = resId.packageId();
+        type->id = resId.typeId();
+        entry->id = resId.entryId();
     }
     return true;
 }
 
-bool ResourceTable::merge(ResourceTable&& other) {
-    const bool mangleNames = mPackage != other.getPackage();
-    std::u16string mangledName;
-
-    for (auto& otherType : other) {
-        std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
-        if (otherType->publicStatus.isPublic) {
-            if (type->publicStatus.isPublic && type->typeId != otherType->typeId) {
-                Logger::error() << "can not merge type '" << type->type
-                                << "': conflicting public IDs "
-                                << "(" << type->typeId << " vs " << otherType->typeId << ")."
-                                << std::endl;
-                return false;
-            }
-            type->publicStatus = std::move(otherType->publicStatus);
-            type->typeId = otherType->typeId;
-        }
-
-        for (auto& otherEntry : otherType->entries) {
-            const std::u16string* nameToAdd = &otherEntry->name;
-            if (mangleNames) {
-                mangledName = otherEntry->name;
-                NameMangler::mangle(other.getPackage(), &mangledName);
-                nameToAdd = &mangledName;
-            }
-
-            std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
-            if (otherEntry->publicStatus.isPublic) {
-                if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) {
-                    Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
-                                    << "': conflicting public IDs "
-                                    << "(" << entry->entryId << " vs " << entry->entryId << ")."
-                                    << std::endl;
-                    return false;
-                }
-                entry->publicStatus = std::move(otherEntry->publicStatus);
-                entry->entryId = otherEntry->entryId;
-            }
-
-            for (ResourceConfigValue& otherValue : otherEntry->values) {
-                auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
-                                             otherValue.config, compareConfigs);
-                if (iter != entry->values.end() && iter->config == otherValue.config) {
-                    int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
-                    if (collisionResult > 0) {
-                        // Take the incoming value.
-                        iter->source = std::move(otherValue.source);
-                        iter->comment = std::move(otherValue.comment);
-                        iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
-                    } else if (collisionResult == 0) {
-                        ResourceNameRef resourceName = { mPackage, type->type, entry->name };
-                        Logger::error(otherValue.source)
-                                << "resource '" << resourceName << "' has a conflicting value for "
-                                << "configuration (" << otherValue.config << ")."
-                                << std::endl;
-                        Logger::note(iter->source) << "originally defined here." << std::endl;
-                        return false;
-                    }
-                } else {
-                    entry->values.insert(iter, ResourceConfigValue{
-                            otherValue.config,
-                            std::move(otherValue.source),
-                            std::move(otherValue.comment),
-                            std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
-                    });
-                }
-            }
-        }
-    }
-    return true;
-}
-
-std::tuple<const ResourceTableType*, const ResourceEntry*>
-ResourceTable::findResource(const ResourceNameRef& name) const {
-    if (name.package != mPackage) {
+Maybe<ResourceTable::SearchResult>
+ResourceTable::findResource(const ResourceNameRef& name) {
+    ResourceTablePackage* package = findPackage(name.package);
+    if (!package) {
         return {};
     }
 
-    auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
-    if (iter == mTypes.end() || (*iter)->type != name.type) {
+    ResourceTableType* type = package->findType(name.type);
+    if (!type) {
         return {};
     }
 
-    const std::unique_ptr<ResourceTableType>& type = *iter;
-    auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
-                                  lessThanEntry);
-    if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
+    ResourceEntry* entry = type->findEntry(name.entry);
+    if (!entry) {
         return {};
     }
-    return std::make_tuple(iter->get(), iter2->get());
+    return SearchResult{ package, type, entry };
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 706f56a..be90936 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -18,6 +18,7 @@
 #define AAPT_RESOURCE_TABLE_H
 
 #include "ConfigDescription.h"
+#include "Diagnostics.h"
 #include "Resource.h"
 #include "ResourceValues.h"
 #include "Source.h"
@@ -30,12 +31,18 @@
 
 namespace aapt {
 
+enum class SymbolState {
+    kUndefined,
+    kPublic,
+    kPrivate
+};
+
 /**
  * The Public status of a resource.
  */
-struct Public {
-    bool isPublic = false;
-    SourceLine source;
+struct Symbol {
+    SymbolState state = SymbolState::kUndefined;
+    Source source;
     std::u16string comment;
 };
 
@@ -44,7 +51,7 @@
  */
 struct ResourceConfigValue {
     ConfigDescription config;
-    SourceLine source;
+    Source source;
     std::u16string comment;
     std::unique_ptr<Value> value;
 };
@@ -54,10 +61,6 @@
  * varying values for each defined configuration.
  */
 struct ResourceEntry {
-    enum {
-        kUnsetEntryId = 0xffffffffu
-    };
-
     /**
      * The name of the resource. Immutable, as
      * this determines the order of this resource
@@ -68,21 +71,20 @@
     /**
      * The entry ID for this resource.
      */
-    size_t entryId;
+    Maybe<uint16_t> id;
 
     /**
      * Whether this resource is public (and must maintain the same
      * entry ID across builds).
      */
-    Public publicStatus;
+    Symbol symbolStatus;
 
     /**
      * The resource's values for each configuration.
      */
     std::vector<ResourceConfigValue> values;
 
-    inline ResourceEntry(const StringPiece16& _name);
-    inline ResourceEntry(const ResourceEntry* rhs);
+    ResourceEntry(const StringPiece16& name) : name(name.toString()) { }
 };
 
 /**
@@ -90,10 +92,6 @@
  * for this type.
  */
 struct ResourceTableType {
-    enum {
-        kUnsetTypeId = 0xffffffffu
-    };
-
     /**
      * The logical type of resource (string, drawable, layout, etc.).
      */
@@ -102,21 +100,43 @@
     /**
      * The type ID for this resource.
      */
-    size_t typeId;
+    Maybe<uint8_t> id;
 
     /**
      * Whether this type is public (and must maintain the same
      * type ID across builds).
      */
-    Public publicStatus;
+    Symbol symbolStatus;
 
     /**
      * List of resources for this type.
      */
     std::vector<std::unique_ptr<ResourceEntry>> entries;
 
-    ResourceTableType(const ResourceType _type);
-    ResourceTableType(const ResourceTableType* rhs);
+    explicit ResourceTableType(const ResourceType type) : type(type) { }
+
+    ResourceEntry* findEntry(const StringPiece16& name);
+
+    ResourceEntry* findOrCreateEntry(const StringPiece16& name);
+};
+
+enum class PackageType {
+    System,
+    Vendor,
+    App,
+    Dynamic
+};
+
+struct ResourceTablePackage {
+    PackageType type = PackageType::App;
+    Maybe<uint8_t> id;
+    std::u16string name;
+
+    std::vector<std::unique_ptr<ResourceTableType>> types;
+
+    ResourceTableType* findType(ResourceType type);
+
+    ResourceTableType* findOrCreateType(const ResourceType type);
 };
 
 /**
@@ -125,23 +145,28 @@
  */
 class ResourceTable {
 public:
-    using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator;
-    using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator;
+    ResourceTable() = default;
+    ResourceTable(const ResourceTable&) = delete;
+    ResourceTable& operator=(const ResourceTable&) = delete;
 
-    enum {
-        kUnsetPackageId = 0xffffffff
-    };
-
-    ResourceTable();
-
-    size_t getPackageId() const;
-    void setPackageId(size_t packageId);
-
-    const std::u16string& getPackage() const;
-    void setPackage(const StringPiece16& package);
+    /**
+     * When a collision of resources occurs, this method decides which value to keep.
+     * Returns -1 if the existing value should be chosen.
+     * Returns 0 if the collision can not be resolved (error).
+     * Returns 1 if the incoming value should be chosen.
+     */
+    static int resolveValueCollision(Value* existing, Value* incoming);
 
     bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                     const SourceLine& source, std::unique_ptr<Value> value);
+                     const Source& source, std::unique_ptr<Value> value,
+                     IDiagnostics* diag);
+
+    bool addResource(const ResourceNameRef& name, const ResourceId resId,
+                     const ConfigDescription& config, const Source& source,
+                     std::unique_ptr<Value> value, IDiagnostics* diag);
+
+    bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                          const Source& source, const StringPiece16& path, IDiagnostics* diag);
 
     /**
      * Same as addResource, but doesn't verify the validity of the name. This is used
@@ -149,129 +174,65 @@
      * names.
      */
     bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                                 const SourceLine& source, std::unique_ptr<Value> value);
+                                 const Source& source, std::unique_ptr<Value> value,
+                                 IDiagnostics* diag);
 
-    bool addResource(const ResourceNameRef& name, const ResourceId resId,
-                     const ConfigDescription& config, const SourceLine& source,
-                     std::unique_ptr<Value> value);
+    bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
+                                 const ConfigDescription& config,
+                                 const Source& source, std::unique_ptr<Value> value,
+                                 IDiagnostics* diag);
 
-    bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
-    bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                const SourceLine& source);
+    bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, const Source& source,
+                        SymbolState state, IDiagnostics* diag);
+    bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                    const Source& source, SymbolState state, IDiagnostics* diag);
+    struct SearchResult {
+        ResourceTablePackage* package;
+        ResourceTableType* type;
+        ResourceEntry* entry;
+    };
 
-    /*
-     * Merges the resources from `other` into this table, mangling the names of the resources
-     * if `other` has a different package name.
-     */
-    bool merge(ResourceTable&& other);
+    Maybe<SearchResult> findResource(const ResourceNameRef& name);
 
     /**
-     * Returns the string pool used by this ResourceTable.
-     * Values that reference strings should use this pool to create
-     * their strings.
+     * The string pool used by this resource table. Values that reference strings must use
+     * this pool to create their strings.
+     *
+     * NOTE: `stringPool` must come before `packages` so that it is destroyed after.
+     * When `string pool` references are destroyed (as they will be when `packages`
+     * is destroyed), they decrement a refCount, which would cause invalid
+     * memory access if the pool was already destroyed.
      */
-    StringPool& getValueStringPool();
-    const StringPool& getValueStringPool() const;
+    StringPool stringPool;
 
-    std::tuple<const ResourceTableType*, const ResourceEntry*>
-    findResource(const ResourceNameRef& name) const;
+    /**
+     * The list of packages in this table, sorted alphabetically by package name.
+     */
+    std::vector<std::unique_ptr<ResourceTablePackage>> packages;
 
-    iterator begin();
-    iterator end();
-    const_iterator begin() const;
-    const_iterator end() const;
+    /**
+     * Returns the package struct with the given name, or nullptr if such a package does not
+     * exist. The empty string is a valid package and typically is used to represent the
+     * 'current' package before it is known to the ResourceTable.
+     */
+    ResourceTablePackage* findPackage(const StringPiece16& name);
+
+    ResourceTablePackage* findPackageById(uint8_t id);
+
+    ResourceTablePackage* createPackage(const StringPiece16& name, Maybe<uint8_t> id = {});
 
 private:
-    std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type);
-    std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
-                                                      const StringPiece16& name);
+    ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
 
     bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
-                         const ConfigDescription& config, const SourceLine& source,
-                         std::unique_ptr<Value> value, const char16_t* validChars);
-    bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
-                        const SourceLine& source, const char16_t* validChars);
-
-    std::u16string mPackage;
-    size_t mPackageId;
-
-    // StringPool must come before mTypes so that it is destroyed after.
-    // When StringPool references are destroyed (as they will be when mTypes
-    // is destroyed), they decrement a refCount, which would cause invalid
-    // memory access if the pool was already destroyed.
-    StringPool mValuePool;
-
-    std::vector<std::unique_ptr<ResourceTableType>> mTypes;
+                         const ConfigDescription& config, const Source& source,
+                         std::unique_ptr<Value> value, const char16_t* validChars,
+                         IDiagnostics* diag);
+    bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
+                            const Source& source, SymbolState state, const char16_t* validChars,
+                            IDiagnostics* diag);
 };
 
-//
-// ResourceEntry implementation.
-//
-
-inline ResourceEntry::ResourceEntry(const StringPiece16& _name) :
-        name(_name.toString()), entryId(kUnsetEntryId) {
-}
-
-inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) :
-        name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) {
-}
-
-//
-// ResourceTableType implementation.
-//
-
-inline ResourceTableType::ResourceTableType(const ResourceType _type) :
-        type(_type), typeId(kUnsetTypeId) {
-}
-
-inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) :
-        type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) {
-}
-
-//
-// ResourceTable implementation.
-//
-
-inline StringPool& ResourceTable::getValueStringPool() {
-    return mValuePool;
-}
-
-inline const StringPool& ResourceTable::getValueStringPool() const {
-    return mValuePool;
-}
-
-inline ResourceTable::iterator ResourceTable::begin() {
-    return mTypes.begin();
-}
-
-inline ResourceTable::iterator ResourceTable::end() {
-    return mTypes.end();
-}
-
-inline ResourceTable::const_iterator ResourceTable::begin() const {
-    return mTypes.begin();
-}
-
-inline ResourceTable::const_iterator ResourceTable::end() const {
-    return mTypes.end();
-}
-
-inline const std::u16string& ResourceTable::getPackage() const {
-    return mPackage;
-}
-
-inline size_t ResourceTable::getPackageId() const {
-    return mPackageId;
-}
-
-inline void ResourceTable::setPackage(const StringPiece16& package) {
-    mPackage = package.toString();
-}
-
-inline void ResourceTable::setPackageId(size_t packageId) {
-    mPackageId = packageId;
-}
-
 } // namespace aapt
 
 #endif // AAPT_RESOURCE_TABLE_H
diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp
deleted file mode 100644
index 910c2c0..0000000
--- a/tools/aapt2/ResourceTableResolver.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Maybe.h"
-#include "NameMangler.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceTableResolver.h"
-#include "ResourceValues.h"
-#include "Util.h"
-
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <memory>
-#include <vector>
-
-namespace aapt {
-
-ResourceTableResolver::ResourceTableResolver(
-        std::shared_ptr<const ResourceTable> table,
-        const std::vector<std::shared_ptr<const android::AssetManager>>& sources) :
-        mTable(table), mSources(sources) {
-    for (const auto& assetManager : mSources) {
-        const android::ResTable& resTable = assetManager->getResources(false);
-        const size_t packageCount = resTable.getBasePackageCount();
-        for (size_t i = 0; i < packageCount; i++) {
-            std::u16string packageName = resTable.getBasePackageName(i).string();
-            mIncludedPackages.insert(std::move(packageName));
-        }
-    }
-}
-
-Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) {
-    Maybe<Entry> result = findAttribute(name);
-    if (result) {
-        return result.value().id;
-    }
-    return {};
-}
-
-Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) {
-    auto cacheIter = mCache.find(name);
-    if (cacheIter != std::end(mCache)) {
-        return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
-    }
-
-    ResourceName mangledName;
-    const ResourceName* nameToSearch = &name;
-    if (name.package != mTable->getPackage()) {
-        // This may be a reference to an included resource or
-        // to a mangled resource.
-        if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
-            // This is not in our included set, so mangle the name and
-            // check for that.
-            mangledName.entry = name.entry;
-            NameMangler::mangle(name.package, &mangledName.entry);
-            mangledName.package = mTable->getPackage();
-            mangledName.type = name.type;
-            nameToSearch = &mangledName;
-        } else {
-            const CacheEntry* cacheEntry = buildCacheEntry(name);
-            if (cacheEntry) {
-                return Entry{ cacheEntry->id, cacheEntry->attr.get() };
-            }
-            return {};
-        }
-    }
-
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = mTable->findResource(*nameToSearch);
-    if (type && entry) {
-        Entry result = {};
-        if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
-                type->typeId != ResourceTableType::kUnsetTypeId &&
-                entry->entryId != ResourceEntry::kUnsetEntryId) {
-            result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId);
-        }
-
-        if (!entry->values.empty()) {
-            visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) {
-                    result.attr = &attr;
-            });
-        }
-        return result;
-    }
-    return {};
-}
-
-Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) {
-    for (const auto& assetManager : mSources) {
-        const android::ResTable& table = assetManager->getResources(false);
-
-        android::ResTable::resource_name resourceName;
-        if (!table.getResourceName(resId.id, false, &resourceName)) {
-            continue;
-        }
-
-        const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
-                                                                   resourceName.typeLen));
-        assert(type);
-        return ResourceName{
-                { resourceName.package, resourceName.packageLen },
-                *type,
-                { resourceName.name, resourceName.nameLen } };
-    }
-    return {};
-}
-
-/**
- * This is called when we need to lookup a resource name in the AssetManager.
- * Since the values in the AssetManager are not parsed like in a ResourceTable,
- * we must create Attribute objects here if we find them.
- */
-const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry(
-        const ResourceName& name) {
-    for (const auto& assetManager : mSources) {
-        const android::ResTable& table = assetManager->getResources(false);
-
-        const StringPiece16 type16 = toString(name.type);
-        ResourceId resId {
-            table.identifierForName(
-                    name.entry.data(), name.entry.size(),
-                    type16.data(), type16.size(),
-                    name.package.data(), name.package.size())
-        };
-
-        if (!resId.isValid()) {
-            continue;
-        }
-
-        CacheEntry& entry = mCache[name];
-        entry.id = resId;
-
-        //
-        // Now check to see if this resource is an Attribute.
-        //
-
-        const android::ResTable::bag_entry* bagBegin;
-        ssize_t bags = table.lockBag(resId.id, &bagBegin);
-        if (bags < 1) {
-            table.unlockBag(bagBegin);
-            return &entry;
-        }
-
-        // Look for the ATTR_TYPE key in the bag and check the types it supports.
-        uint32_t attrTypeMask = 0;
-        for (ssize_t i = 0; i < bags; i++) {
-            if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
-                attrTypeMask = bagBegin[i].map.value.data;
-            }
-        }
-
-        entry.attr = util::make_unique<Attribute>(false);
-
-        if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
-                attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
-            for (ssize_t i = 0; i < bags; i++) {
-                if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
-                    // Internal IDs are special keys, which are not enum/flag symbols, so skip.
-                    continue;
-                }
-
-                android::ResTable::resource_name symbolName;
-                bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
-                        &symbolName);
-                assert(result);
-                const ResourceType* type = parseResourceType(
-                        StringPiece16(symbolName.type, symbolName.typeLen));
-                assert(type);
-
-                entry.attr->symbols.push_back(Attribute::Symbol{
-                        Reference(ResourceNameRef(
-                                    StringPiece16(symbolName.package, symbolName.packageLen),
-                                    *type,
-                                    StringPiece16(symbolName.name, symbolName.nameLen))),
-                                bagBegin[i].map.value.data
-                });
-            }
-        }
-
-        entry.attr->typeMask |= attrTypeMask;
-        table.unlockBag(bagBegin);
-        return &entry;
-    }
-    return nullptr;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h
deleted file mode 100644
index 8f6b0b5..0000000
--- a/tools/aapt2/ResourceTableResolver.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_RESOURCE_TABLE_RESOLVER_H
-#define AAPT_RESOURCE_TABLE_RESOLVER_H
-
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-
-#include <androidfw/AssetManager.h>
-#include <memory>
-#include <vector>
-#include <unordered_set>
-
-namespace aapt {
-
-/**
- * Encapsulates the search of library sources as well as the local ResourceTable.
- */
-class ResourceTableResolver : public IResolver {
-public:
-    /**
-     * Creates a resolver with a local ResourceTable and an AssetManager
-     * loaded with library packages.
-     */
-    ResourceTableResolver(
-            std::shared_ptr<const ResourceTable> table,
-            const std::vector<std::shared_ptr<const android::AssetManager>>& sources);
-
-    ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable.
-
-    virtual Maybe<ResourceId> findId(const ResourceName& name) override;
-
-    virtual Maybe<Entry> findAttribute(const ResourceName& name) override;
-
-    virtual Maybe<ResourceName> findName(ResourceId resId) override;
-
-private:
-    struct CacheEntry {
-        ResourceId id;
-        std::unique_ptr<Attribute> attr;
-    };
-
-    const CacheEntry* buildCacheEntry(const ResourceName& name);
-
-    std::shared_ptr<const ResourceTable> mTable;
-    std::vector<std::shared_ptr<const android::AssetManager>> mSources;
-    std::map<ResourceName, CacheEntry> mCache;
-    std::unordered_set<std::u16string> mIncludedPackages;
-};
-
-} // namespace aapt
-
-#endif // AAPT_RESOURCE_TABLE_RESOLVER_H
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 06d8699..2055a80 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+#include "Diagnostics.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+
+#include "test/Common.h"
 
 #include <algorithm>
 #include <gtest/gtest.h>
@@ -25,204 +28,90 @@
 
 namespace aapt {
 
-struct TestValue : public Value {
-    std::u16string value;
+struct ResourceTableTest : public ::testing::Test {
+    struct EmptyDiagnostics : public IDiagnostics {
+        void error(const DiagMessage& msg) override {}
+        void warn(const DiagMessage& msg) override {}
+        void note(const DiagMessage& msg) override {}
+    };
 
-    TestValue(StringPiece16 str) : value(str.toString()) {
-    }
-
-    TestValue* clone(StringPool* /*newPool*/) const override {
-        return new TestValue(value);
-    }
-
-    void print(std::ostream& out) const override {
-        out << "(test) " << value;
-    }
-
-    virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
-    virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
+    EmptyDiagnostics mDiagnostics;
 };
 
-struct TestWeakValue : public Value {
-    bool isWeak() const override {
-        return true;
-    }
-
-    TestWeakValue* clone(StringPool* /*newPool*/) const override {
-        return new TestWeakValue();
-    }
-
-    void print(std::ostream& out) const override {
-        out << "(test) [weak]";
-    }
-
-    virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
-    virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
-};
-
-TEST(ResourceTableTest, FailToAddResourceWithBadName) {
+TEST_F(ResourceTableTest, FailToAddResourceWithBadName) {
     ResourceTable table;
-    table.setPackage(u"android");
 
     EXPECT_FALSE(table.addResource(
             ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
-            {}, SourceLine{ "test.xml", 21 },
-            util::make_unique<TestValue>(u"rawValue")));
+            {}, Source{ "test.xml", 21 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_FALSE(table.addResource(
             ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
-            {}, SourceLine{ "test.xml", 21 },
-            util::make_unique<TestValue>(u"rawValue")));
+            {}, Source{ "test.xml", 21 },
+            util::make_unique<Id>(), &mDiagnostics));
 }
 
-TEST(ResourceTableTest, AddOneResource) {
-    const std::u16string kAndroidPackage = u"android";
-
+TEST_F(ResourceTableTest, AddOneResource) {
     ResourceTable table;
-    table.setPackage(kAndroidPackage);
 
-    const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" };
+    EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {},
+                                  Source{ "test/path/file.xml", 23 },
+                                  util::make_unique<Id>(), &mDiagnostics));
 
-    EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 },
-                                  util::make_unique<TestValue>(u"rawValue")));
-
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = table.findResource(name);
-    ASSERT_NE(nullptr, type);
-    ASSERT_NE(nullptr, entry);
-    EXPECT_EQ(name.entry, entry->name);
-
-    ASSERT_NE(std::end(entry->values),
-              std::find_if(std::begin(entry->values), std::end(entry->values),
-                      [](const ResourceConfigValue& val) -> bool {
-                          return val.config == ConfigDescription{};
-                      }));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
 }
 
-TEST(ResourceTableTest, AddMultipleResources) {
-    const std::u16string kAndroidPackage = u"android";
+TEST_F(ResourceTableTest, AddMultipleResources) {
     ResourceTable table;
-    table.setPackage(kAndroidPackage);
 
     ConfigDescription config;
     ConfigDescription languageConfig;
     memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" },
-            config, SourceLine{ "test/path/file.xml", 10 },
-            util::make_unique<TestValue>(u"rawValue")));
+            test::parseNameOrDie(u"@android:attr/layout_width"),
+            config, Source{ "test/path/file.xml", 10 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" },
-            config, SourceLine{ "test/path/file.xml", 12 },
-            util::make_unique<TestValue>(u"rawValue")));
+            test::parseNameOrDie(u"@android:attr/id"),
+            config, Source{ "test/path/file.xml", 12 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
-            config, SourceLine{ "test/path/file.xml", 14 },
-            util::make_unique<TestValue>(u"Ok")));
+            test::parseNameOrDie(u"@android:string/ok"),
+            config, Source{ "test/path/file.xml", 14 },
+            util::make_unique<Id>(), &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
-            ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
-            languageConfig, SourceLine{ "test/path/file.xml", 20 },
-            util::make_unique<TestValue>(u"Tak")));
+            test::parseNameOrDie(u"@android:string/ok"),
+            languageConfig, Source{ "test/path/file.xml", 20 },
+            util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics));
 
-    const auto endTypeIter = std::end(table);
-    auto typeIter = std::begin(table);
-
-    ASSERT_NE(endTypeIter, typeIter);
-    EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type);
-
-    {
-        const std::unique_ptr<ResourceTableType>& type = *typeIter;
-        const auto endEntryIter = std::end(type->entries);
-        auto entryIter = std::begin(type->entries);
-        ASSERT_NE(endEntryIter, entryIter);
-        EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name);
-
-        ++entryIter;
-        ASSERT_NE(endEntryIter, entryIter);
-        EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name);
-
-        ++entryIter;
-        ASSERT_EQ(endEntryIter, entryIter);
-    }
-
-    ++typeIter;
-    ASSERT_NE(endTypeIter, typeIter);
-    EXPECT_EQ(ResourceType::kString, (*typeIter)->type);
-
-    {
-        const std::unique_ptr<ResourceTableType>& type = *typeIter;
-        const auto endEntryIter = std::end(type->entries);
-        auto entryIter = std::begin(type->entries);
-        ASSERT_NE(endEntryIter, entryIter);
-        EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name);
-
-        {
-            const std::unique_ptr<ResourceEntry>& entry = *entryIter;
-            const auto endConfigIter = std::end(entry->values);
-            auto configIter = std::begin(entry->values);
-
-            ASSERT_NE(endConfigIter, configIter);
-            EXPECT_EQ(config, configIter->config);
-            const TestValue* value =
-                    dynamic_cast<const TestValue*>(configIter->value.get());
-            ASSERT_NE(nullptr, value);
-            EXPECT_EQ(std::u16string(u"Ok"), value->value);
-
-            ++configIter;
-            ASSERT_NE(endConfigIter, configIter);
-            EXPECT_EQ(languageConfig, configIter->config);
-            EXPECT_NE(nullptr, configIter->value);
-
-            value = dynamic_cast<const TestValue*>(configIter->value.get());
-            ASSERT_NE(nullptr, value);
-            EXPECT_EQ(std::u16string(u"Tak"), value->value);
-
-            ++configIter;
-            EXPECT_EQ(endConfigIter, configIter);
-        }
-
-        ++entryIter;
-        ASSERT_EQ(endEntryIter, entryIter);
-    }
-
-    ++typeIter;
-    EXPECT_EQ(endTypeIter, typeIter);
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:string/ok"));
+    ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, u"@android:string/ok",
+                                                                languageConfig));
 }
 
-TEST(ResourceTableTest, OverrideWeakResourceValue) {
-    const std::u16string kAndroid = u"android";
-
+TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
     ResourceTable table;
-    table.setPackage(kAndroid);
-    table.setPackageId(0x01);
 
-    ASSERT_TRUE(table.addResource(
-            ResourceName{ kAndroid, ResourceType::kAttr, u"foo" },
-            {}, {}, util::make_unique<TestWeakValue>()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+                                  util::make_unique<Attribute>(true), &mDiagnostics));
 
-    const ResourceTableType* type;
-    const ResourceEntry* entry;
-    std::tie(type, entry) = table.findResource(
-            ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
-    ASSERT_NE(nullptr, type);
-    ASSERT_NE(nullptr, entry);
-    ASSERT_EQ(entry->values.size(), 1u);
-    EXPECT_TRUE(entry->values.front().value->isWeak());
+    Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_TRUE(attr->isWeak());
 
-    ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {},
-                                  util::make_unique<TestValue>(u"bar")));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+                                  util::make_unique<Attribute>(false), &mDiagnostics));
 
-    std::tie(type, entry) = table.findResource(
-            ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
-    ASSERT_NE(nullptr, type);
-    ASSERT_NE(nullptr, entry);
-    ASSERT_EQ(entry->values.size(), 1u);
-    EXPECT_FALSE(entry->values.front().value->isWeak());
+    attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_FALSE(attr->isWeak());
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
new file mode 100644
index 0000000..0db1c37
--- /dev/null
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceUtils.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <sstream>
+
+namespace aapt {
+namespace ResourceUtils {
+
+void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+                         StringPiece16* outType, StringPiece16* outEntry) {
+    const char16_t* start = str.data();
+    const char16_t* end = start + str.size();
+    const char16_t* current = start;
+    while (current != end) {
+        if (outType->size() == 0 && *current == u'/') {
+            outType->assign(start, current - start);
+            start = current + 1;
+        } else if (outPackage->size() == 0 && *current == u':') {
+            outPackage->assign(start, current - start);
+            start = current + 1;
+        }
+        current++;
+    }
+    outEntry->assign(start, end - start);
+}
+
+bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
+                       bool* outPrivate) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    if (trimmedStr.empty()) {
+        return false;
+    }
+
+    bool create = false;
+    bool priv = false;
+    if (trimmedStr.data()[0] == u'@') {
+        size_t offset = 1;
+        if (trimmedStr.data()[1] == u'+') {
+            create = true;
+            offset += 1;
+        } else if (trimmedStr.data()[1] == u'*') {
+            priv = true;
+            offset += 1;
+        }
+        StringPiece16 package;
+        StringPiece16 type;
+        StringPiece16 entry;
+        extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type,
+                            &entry);
+
+        const ResourceType* parsedType = parseResourceType(type);
+        if (!parsedType) {
+            return false;
+        }
+
+        if (create && *parsedType != ResourceType::kId) {
+            return false;
+        }
+
+        outRef->package = package;
+        outRef->type = *parsedType;
+        outRef->entry = entry;
+        if (outCreate) {
+            *outCreate = create;
+        }
+        if (outPrivate) {
+            *outPrivate = priv;
+        }
+        return true;
+    }
+    return false;
+}
+
+bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    if (trimmedStr.empty()) {
+        return false;
+    }
+
+    if (*trimmedStr.data() == u'?') {
+        StringPiece16 package;
+        StringPiece16 type;
+        StringPiece16 entry;
+        extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+
+        if (!type.empty() && type != u"attr") {
+            return false;
+        }
+
+        outRef->package = package;
+        outRef->type = ResourceType::kAttr;
+        outRef->entry = entry;
+        return true;
+    }
+    return false;
+}
+
+/*
+ * Style parent's are a bit different. We accept the following formats:
+ *
+ * @[package:]style/<entry>
+ * ?[package:]style/<entry>
+ * <package>:[style/]<entry>
+ * [package:style/]<entry>
+ */
+Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
+    if (str.empty()) {
+        return {};
+    }
+
+    StringPiece16 name = str;
+
+    bool hasLeadingIdentifiers = false;
+    bool privateRef = false;
+
+    // Skip over these identifiers. A style's parent is a normal reference.
+    if (name.data()[0] == u'@' || name.data()[0] == u'?') {
+        hasLeadingIdentifiers = true;
+        name = name.substr(1, name.size() - 1);
+        if (name.data()[0] == u'*') {
+            privateRef = true;
+            name = name.substr(1, name.size() - 1);
+        }
+    }
+
+    ResourceNameRef ref;
+    ref.type = ResourceType::kStyle;
+
+    StringPiece16 typeStr;
+    extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+    if (!typeStr.empty()) {
+        // If we have a type, make sure it is a Style.
+        const ResourceType* parsedType = parseResourceType(typeStr);
+        if (!parsedType || *parsedType != ResourceType::kStyle) {
+            std::stringstream err;
+            err << "invalid resource type '" << typeStr << "' for parent of style";
+            *outError = err.str();
+            return {};
+        }
+    } else {
+        // No type was defined, this should not have a leading identifier.
+        if (hasLeadingIdentifiers) {
+            std::stringstream err;
+            err << "invalid parent reference '" << str << "'";
+            *outError = err.str();
+            return {};
+        }
+    }
+
+    if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+        std::stringstream err;
+        err << "invalid parent reference '" << str << "'";
+        *outError = err.str();
+        return {};
+    }
+
+    Reference result(ref);
+    result.privateReference = privateRef;
+    return result;
+}
+
+std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
+    ResourceNameRef ref;
+    bool privateRef = false;
+    if (tryParseReference(str, &ref, outCreate, &privateRef)) {
+        std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+        value->privateReference = privateRef;
+        return value;
+    }
+
+    if (tryParseAttributeReference(str, &ref)) {
+        if (outCreate) {
+            *outCreate = false;
+        }
+        return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+    }
+    return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    android::Res_value value = { };
+    if (trimmedStr == u"@null") {
+        // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
+        // Instead we set the data type to TYPE_REFERENCE with a value of 0.
+        value.dataType = android::Res_value::TYPE_REFERENCE;
+    } else if (trimmedStr == u"@empty") {
+        // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
+        value.dataType = android::Res_value::TYPE_NULL;
+        value.data = android::Res_value::DATA_NULL_EMPTY;
+    } else {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+                                                    const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    for (const Attribute::Symbol& symbol : enumAttr->symbols) {
+        // Enum symbols are stored as @package:id/symbol resources,
+        // so we need to match against the 'entry' part of the identifier.
+        const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
+        if (trimmedStr == enumSymbolResourceName.entry) {
+            android::Res_value value = { };
+            value.dataType = android::Res_value::TYPE_INT_DEC;
+            value.data = symbol.value;
+            return util::make_unique<BinaryPrimitive>(value);
+        }
+    }
+    return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
+                                                    const StringPiece16& str) {
+    android::Res_value flags = { };
+    flags.dataType = android::Res_value::TYPE_INT_DEC;
+
+    for (StringPiece16 part : util::tokenize(str, u'|')) {
+        StringPiece16 trimmedPart = util::trimWhitespace(part);
+
+        bool flagSet = false;
+        for (const Attribute::Symbol& symbol : flagAttr->symbols) {
+            // Flag symbols are stored as @package:id/symbol resources,
+            // so we need to match against the 'entry' part of the identifier.
+            const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
+            if (trimmedPart == flagSymbolResourceName.entry) {
+                flags.data |= symbol.value;
+                flagSet = true;
+                break;
+            }
+        }
+
+        if (!flagSet) {
+            return {};
+        }
+    }
+    return util::make_unique<BinaryPrimitive>(flags);
+}
+
+static uint32_t parseHex(char16_t c, bool* outError) {
+    if (c >= u'0' && c <= u'9') {
+        return c - u'0';
+    } else if (c >= u'a' && c <= u'f') {
+        return c - u'a' + 0xa;
+    } else if (c >= u'A' && c <= u'F') {
+        return c - u'A' + 0xa;
+    } else {
+        *outError = true;
+        return 0xffffffffu;
+    }
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
+    StringPiece16 colorStr(util::trimWhitespace(str));
+    const char16_t* start = colorStr.data();
+    const size_t len = colorStr.size();
+    if (len == 0 || start[0] != u'#') {
+        return {};
+    }
+
+    android::Res_value value = { };
+    bool error = false;
+    if (len == 4) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+        value.data = 0xff000000u;
+        value.data |= parseHex(start[1], &error) << 20;
+        value.data |= parseHex(start[1], &error) << 16;
+        value.data |= parseHex(start[2], &error) << 12;
+        value.data |= parseHex(start[2], &error) << 8;
+        value.data |= parseHex(start[3], &error) << 4;
+        value.data |= parseHex(start[3], &error);
+    } else if (len == 5) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+        value.data |= parseHex(start[1], &error) << 28;
+        value.data |= parseHex(start[1], &error) << 24;
+        value.data |= parseHex(start[2], &error) << 20;
+        value.data |= parseHex(start[2], &error) << 16;
+        value.data |= parseHex(start[3], &error) << 12;
+        value.data |= parseHex(start[3], &error) << 8;
+        value.data |= parseHex(start[4], &error) << 4;
+        value.data |= parseHex(start[4], &error);
+    } else if (len == 7) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+        value.data = 0xff000000u;
+        value.data |= parseHex(start[1], &error) << 20;
+        value.data |= parseHex(start[2], &error) << 16;
+        value.data |= parseHex(start[3], &error) << 12;
+        value.data |= parseHex(start[4], &error) << 8;
+        value.data |= parseHex(start[5], &error) << 4;
+        value.data |= parseHex(start[6], &error);
+    } else if (len == 9) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+        value.data |= parseHex(start[1], &error) << 28;
+        value.data |= parseHex(start[2], &error) << 24;
+        value.data |= parseHex(start[3], &error) << 20;
+        value.data |= parseHex(start[4], &error) << 16;
+        value.data |= parseHex(start[5], &error) << 12;
+        value.data |= parseHex(start[6], &error) << 8;
+        value.data |= parseHex(start[7], &error) << 4;
+        value.data |= parseHex(start[8], &error);
+    } else {
+        return {};
+    }
+    return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    uint32_t data = 0;
+    if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
+        data = 0xffffffffu;
+    } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
+        return {};
+    }
+    android::Res_value value = { };
+    value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+    value.data = data;
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
+    android::Res_value value;
+    if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
+    android::Res_value value;
+    if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
+    switch (type) {
+    case android::Res_value::TYPE_NULL:
+    case android::Res_value::TYPE_REFERENCE:
+    case android::Res_value::TYPE_ATTRIBUTE:
+    case android::Res_value::TYPE_DYNAMIC_REFERENCE:
+        return android::ResTable_map::TYPE_REFERENCE;
+
+    case android::Res_value::TYPE_STRING:
+        return android::ResTable_map::TYPE_STRING;
+
+    case android::Res_value::TYPE_FLOAT:
+        return android::ResTable_map::TYPE_FLOAT;
+
+    case android::Res_value::TYPE_DIMENSION:
+        return android::ResTable_map::TYPE_DIMENSION;
+
+    case android::Res_value::TYPE_FRACTION:
+        return android::ResTable_map::TYPE_FRACTION;
+
+    case android::Res_value::TYPE_INT_DEC:
+    case android::Res_value::TYPE_INT_HEX:
+        return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
+                | android::ResTable_map::TYPE_FLAGS;
+
+    case android::Res_value::TYPE_INT_BOOLEAN:
+        return android::ResTable_map::TYPE_BOOLEAN;
+
+    case android::Res_value::TYPE_INT_COLOR_ARGB8:
+    case android::Res_value::TYPE_INT_COLOR_RGB8:
+    case android::Res_value::TYPE_INT_COLOR_ARGB4:
+    case android::Res_value::TYPE_INT_COLOR_RGB4:
+        return android::ResTable_map::TYPE_COLOR;
+
+    default:
+        return 0;
+    };
+}
+
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& value, uint32_t typeMask,
+        std::function<void(const ResourceName&)> onCreateReference) {
+    std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
+    if (nullOrEmpty) {
+        return std::move(nullOrEmpty);
+    }
+
+    bool create = false;
+    std::unique_ptr<Reference> reference = tryParseReference(value, &create);
+    if (reference) {
+        if (create && onCreateReference) {
+            onCreateReference(reference->name.value());
+        }
+        return std::move(reference);
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_COLOR) {
+        // Try parsing this as a color.
+        std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
+        if (color) {
+            return std::move(color);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+        // Try parsing this as a boolean.
+        std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
+        if (boolean) {
+            return std::move(boolean);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+        // Try parsing this as an integer.
+        std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
+        if (integer) {
+            return std::move(integer);
+        }
+    }
+
+    const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
+            | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
+    if (typeMask & floatMask) {
+        // Try parsing this as a float.
+        std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
+        if (floatingPoint) {
+            if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
+                return std::move(floatingPoint);
+            }
+        }
+    }
+    return {};
+}
+
+/**
+ * We successively try to parse the string as a resource type that the Attribute
+ * allows.
+ */
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& str, const Attribute* attr,
+        std::function<void(const ResourceName&)> onCreateReference) {
+    const uint32_t typeMask = attr->typeMask;
+    std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
+    if (value) {
+        return value;
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_ENUM) {
+        // Try parsing this as an enum.
+        std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
+        if (enumValue) {
+            return std::move(enumValue);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+        // Try parsing this as a flag.
+        std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
+        if (flagValue) {
+            return std::move(flagValue);
+        }
+    }
+    return {};
+}
+
+} // namespace ResourceUtils
+} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
new file mode 100644
index 0000000..118a2ee
--- /dev/null
+++ b/tools/aapt2/ResourceUtils.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_RESOURCEUTILS_H
+#define AAPT_RESOURCEUTILS_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "util/StringPiece.h"
+
+#include <functional>
+#include <memory>
+
+namespace aapt {
+namespace ResourceUtils {
+
+/*
+ * Extracts the package, type, and name from a string of the format:
+ *
+ *      [package:]type/name
+ *
+ * where the package can be empty. Validation must be performed on each
+ * individual extracted piece to verify that the pieces are valid.
+ */
+void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+                         StringPiece16* outType, StringPiece16* outEntry);
+
+/*
+ * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
+ * `outReference` set to the parsed reference.
+ *
+ * If '+' was present in the reference, `outCreate` is set to true.
+ * If '*' was present in the reference, `outPrivate` is set to true.
+ */
+bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
+                       bool* outCreate = nullptr, bool* outPrivate = nullptr);
+
+/*
+ * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
+ * with `outReference` set to the parsed reference.
+ */
+bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
+
+/*
+ * Returns a Reference, or None Maybe instance if the string `str` was parsed as a
+ * valid reference to a style.
+ * The format for a style parent is slightly more flexible than a normal reference:
+ *
+ * @[package:]style/<entry> or
+ * ?[package:]style/<entry> or
+ * <package>:[style/]<entry>
+ */
+Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError);
+
+/*
+ * Returns a Reference object if the string was parsed as a resource or attribute reference,
+ * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
+ * the '+' was present in the string.
+ */
+std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate = nullptr);
+
+/*
+ * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a color if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a boolean if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing an integer if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a floating point number
+ * (float, dimension, etc) if the string was parsed as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+                                                    const StringPiece16& str);
+
+/*
+ * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
+ * as one.
+ */
+std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
+                                                    const StringPiece16& str);
+/*
+ * Try to convert a string to an Item for the given attribute. The attribute will
+ * restrict what values the string can be converted to.
+ * The callback function onCreateReference is called when the parsed item is a
+ * reference to an ID that must be created (@+id/foo).
+ */
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& value, const Attribute* attr,
+        std::function<void(const ResourceName&)> onCreateReference = {});
+
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& value, uint32_t typeMask,
+        std::function<void(const ResourceName&)> onCreateReference = {});
+
+uint32_t androidTypeToAttributeTypeMask(uint16_t type);
+
+} // namespace ResourceUtils
+} // namespace aapt
+
+#endif /* AAPT_RESOURCEUTILS_H */
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
new file mode 100644
index 0000000..7de8f41
--- /dev/null
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+#include "ResourceUtils.h"
+
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
+    ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
+    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@android:color/foo", &actual, &create,
+                                                 &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
+    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
+                                                 &create, &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
+    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@+android:id/foo", &actual, &create,
+                                                 &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_TRUE(create);
+    EXPECT_FALSE(privateRef);
+}
+
+TEST(ResourceUtilsTest, ParsePrivateReference) {
+    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef actual;
+    bool create = false;
+    bool privateRef = false;
+    EXPECT_TRUE(ResourceUtils::tryParseReference(u"@*android:id/foo", &actual, &create,
+                                                 &privateRef));
+    EXPECT_EQ(expected, actual);
+    EXPECT_FALSE(create);
+    EXPECT_TRUE(privateRef);
+}
+
+TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
+    bool create = false;
+    bool privateRef = false;
+    ResourceNameRef actual;
+    EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create,
+                                                  &privateRef));
+}
+
+TEST(ResourceUtilsTest, ParseStyleParentReference) {
+    const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
+    const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
+
+    std::string errStr;
+    Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"@style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"?android:style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"?style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"android:style/foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"android:foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
+    ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr);
+    AAPT_ASSERT_TRUE(ref);
+    EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index aabb375..ecc5cd2 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,15 +15,26 @@
  */
 
 #include "Resource.h"
-#include "ResourceTypeExtensions.h"
+#include "flatten/ResourceTypeExtensions.h"
 #include "ResourceValues.h"
-#include "Util.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <limits>
 
 namespace aapt {
 
+template <typename Derived>
+void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
+    visitor->visit(static_cast<Derived*>(this));
+}
+
+template <typename Derived>
+void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
+    visitor->visit(static_cast<Derived*>(this));
+}
+
 bool Value::isItem() const {
     return false;
 }
@@ -43,14 +54,14 @@
     return new RawString(newPool->makeRef(*value));
 }
 
-bool RawString::flatten(android::Res_value& outValue) const {
-    outValue.dataType = ExtendedTypes::TYPE_RAW_STRING;
-    outValue.data = static_cast<uint32_t>(value.getIndex());
+bool RawString::flatten(android::Res_value* outValue) const {
+    outValue->dataType = ExtendedTypes::TYPE_RAW_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
     return true;
 }
 
-void RawString::print(std::ostream& out) const {
-    out << "(raw string) " << *value;
+void RawString::print(std::ostream* out) const {
+    *out << "(raw string) " << *value;
 }
 
 Reference::Reference() : referenceType(Reference::Type::kResource) {
@@ -63,11 +74,11 @@
 Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
 }
 
-bool Reference::flatten(android::Res_value& outValue) const {
-    outValue.dataType = (referenceType == Reference::Type::kResource)
+bool Reference::flatten(android::Res_value* outValue) const {
+    outValue->dataType = (referenceType == Reference::Type::kResource)
         ? android::Res_value::TYPE_REFERENCE
         : android::Res_value::TYPE_ATTRIBUTE;
-    outValue.data = id.id;
+    outValue->data = util::hostToDevice32(id ? id.value().id : 0);
     return true;
 }
 
@@ -79,20 +90,20 @@
     return ref;
 }
 
-void Reference::print(std::ostream& out) const {
-    out << "(reference) ";
+void Reference::print(std::ostream* out) const {
+    *out << "(reference) ";
     if (referenceType == Reference::Type::kResource) {
-        out << "@";
+        *out << "@";
     } else {
-        out << "?";
+        *out << "?";
     }
 
-    if (name.isValid()) {
-        out << name;
+    if (name) {
+        *out << name.value();
     }
 
-    if (id.isValid() || Res_INTERNALID(id.id)) {
-        out << " " << id;
+    if (id && !Res_INTERNALID(id.value().id)) {
+        *out << " " << id.value();
     }
 }
 
@@ -100,9 +111,9 @@
     return true;
 }
 
-bool Id::flatten(android::Res_value& out) const {
-    out.dataType = android::Res_value::TYPE_INT_BOOLEAN;
-    out.data = 0;
+bool Id::flatten(android::Res_value* out) const {
+    out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
+    out->data = util::hostToDevice32(0);
     return true;
 }
 
@@ -110,21 +121,21 @@
     return new Id();
 }
 
-void Id::print(std::ostream& out) const {
-    out << "(id)";
+void Id::print(std::ostream* out) const {
+    *out << "(id)";
 }
 
 String::String(const StringPool::Ref& ref) : value(ref) {
 }
 
-bool String::flatten(android::Res_value& outValue) const {
-    // Verify that our StringPool index is within encodeable limits.
+bool String::flatten(android::Res_value* outValue) const {
+    // Verify that our StringPool index is within encode-able limits.
     if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
         return false;
     }
 
-    outValue.dataType = android::Res_value::TYPE_STRING;
-    outValue.data = static_cast<uint32_t>(value.getIndex());
+    outValue->dataType = android::Res_value::TYPE_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
     return true;
 }
 
@@ -132,20 +143,20 @@
     return new String(newPool->makeRef(*value));
 }
 
-void String::print(std::ostream& out) const {
-    out << "(string) \"" << *value << "\"";
+void String::print(std::ostream* out) const {
+    *out << "(string) \"" << *value << "\"";
 }
 
 StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
 }
 
-bool StyledString::flatten(android::Res_value& outValue) const {
+bool StyledString::flatten(android::Res_value* outValue) const {
     if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
         return false;
     }
 
-    outValue.dataType = android::Res_value::TYPE_STRING;
-    outValue.data = static_cast<uint32_t>(value.getIndex());
+    outValue->dataType = android::Res_value::TYPE_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
     return true;
 }
 
@@ -153,20 +164,20 @@
     return new StyledString(newPool->makeRef(value));
 }
 
-void StyledString::print(std::ostream& out) const {
-    out << "(styled string) \"" << *value->str << "\"";
+void StyledString::print(std::ostream* out) const {
+    *out << "(styled string) \"" << *value->str << "\"";
 }
 
 FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
 }
 
-bool FileReference::flatten(android::Res_value& outValue) const {
+bool FileReference::flatten(android::Res_value* outValue) const {
     if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
         return false;
     }
 
-    outValue.dataType = android::Res_value::TYPE_STRING;
-    outValue.data = static_cast<uint32_t>(path.getIndex());
+    outValue->dataType = android::Res_value::TYPE_STRING;
+    outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
     return true;
 }
 
@@ -174,15 +185,21 @@
     return new FileReference(newPool->makeRef(*path));
 }
 
-void FileReference::print(std::ostream& out) const {
-    out << "(file) " << *path;
+void FileReference::print(std::ostream* out) const {
+    *out << "(file) " << *path;
 }
 
 BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
 }
 
-bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
-    outValue = value;
+BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
+    value.dataType = dataType;
+    value.data = data;
+}
+
+bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
+    outValue->dataType = value.dataType;
+    outValue->data = util::hostToDevice32(value.data);
     return true;
 }
 
@@ -190,29 +207,29 @@
     return new BinaryPrimitive(value);
 }
 
-void BinaryPrimitive::print(std::ostream& out) const {
+void BinaryPrimitive::print(std::ostream* out) const {
     switch (value.dataType) {
         case android::Res_value::TYPE_NULL:
-            out << "(null)";
+            *out << "(null)";
             break;
         case android::Res_value::TYPE_INT_DEC:
-            out << "(integer) " << value.data;
+            *out << "(integer) " << value.data;
             break;
         case android::Res_value::TYPE_INT_HEX:
-            out << "(integer) " << std::hex << value.data << std::dec;
+            *out << "(integer) " << std::hex << value.data << std::dec;
             break;
         case android::Res_value::TYPE_INT_BOOLEAN:
-            out << "(boolean) " << (value.data != 0 ? "true" : "false");
+            *out << "(boolean) " << (value.data != 0 ? "true" : "false");
             break;
         case android::Res_value::TYPE_INT_COLOR_ARGB8:
         case android::Res_value::TYPE_INT_COLOR_RGB8:
         case android::Res_value::TYPE_INT_COLOR_ARGB4:
         case android::Res_value::TYPE_INT_COLOR_RGB4:
-            out << "(color) #" << std::hex << value.data << std::dec;
+            *out << "(color) #" << std::hex << value.data << std::dec;
             break;
         default:
-            out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
-                << std::hex << value.data << std::dec;
+            *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
+                 << std::hex << value.data << std::dec;
             break;
     }
 }
@@ -231,9 +248,9 @@
     return attr;
 }
 
-void Attribute::printMask(std::ostream& out) const {
+void Attribute::printMask(std::ostream* out) const {
     if (typeMask == android::ResTable_map::TYPE_ANY) {
-        out << "any";
+        *out << "any";
         return;
     }
 
@@ -242,103 +259,105 @@
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "reference";
+        *out << "reference";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "string";
+        *out << "string";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "integer";
+        *out << "integer";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "boolean";
+        *out << "boolean";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "color";
+        *out << "color";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "float";
+        *out << "float";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "dimension";
+        *out << "dimension";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "fraction";
+        *out << "fraction";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "enum";
+        *out << "enum";
     }
 
     if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
         if (!set) {
             set = true;
         } else {
-            out << "|";
+            *out << "|";
         }
-        out << "flags";
+        *out << "flags";
     }
 }
 
-void Attribute::print(std::ostream& out) const {
-    out << "(attr) ";
+void Attribute::print(std::ostream* out) const {
+    *out << "(attr) ";
     printMask(out);
 
-    out << " ["
-        << util::joiner(symbols.begin(), symbols.end(), ", ")
-        << "]";
+    if (!symbols.empty()) {
+        *out << " ["
+            << util::joiner(symbols.begin(), symbols.end(), ", ")
+            << "]";
+    }
 
     if (weak) {
-        out << " [weak]";
+        *out << " [weak]";
     }
 }
 
@@ -355,19 +374,24 @@
     return style;
 }
 
-void Style::print(std::ostream& out) const {
-    out << "(style) ";
-    if (!parent.name.entry.empty()) {
-        out << parent.name;
+void Style::print(std::ostream* out) const {
+    *out << "(style) ";
+    if (parent && parent.value().name) {
+        *out << parent.value().name.value();
     }
-    out << " ["
+    *out << " ["
         << util::joiner(entries.begin(), entries.end(), ", ")
         << "]";
 }
 
 static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
-    out << value.key.name << " = ";
-    value.value->print(out);
+    if (value.key.name) {
+        out << value.key.name.value();
+    } else {
+        out << "???";
+    }
+    out << " = ";
+    value.value->print(&out);
     return out;
 }
 
@@ -379,8 +403,8 @@
     return array;
 }
 
-void Array::print(std::ostream& out) const {
-    out << "(array) ["
+void Array::print(std::ostream* out) const {
+    *out << "(array) ["
         << util::joiner(items.begin(), items.end(), ", ")
         << "]";
 }
@@ -396,8 +420,8 @@
     return p;
 }
 
-void Plural::print(std::ostream& out) const {
-    out << "(plural)";
+void Plural::print(std::ostream* out) const {
+    *out << "(plural)";
 }
 
 static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
@@ -410,8 +434,8 @@
     return styleable;
 }
 
-void Styleable::print(std::ostream& out) const {
-    out << "(styleable) " << " ["
+void Styleable::print(std::ostream* out) const {
+    *out << "(styleable) " << " ["
         << util::joiner(entries.begin(), entries.end(), ", ")
         << "]";
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index ef6594e..0dae091 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,6 +17,7 @@
 #ifndef AAPT_RESOURCE_VALUES_H
 #define AAPT_RESOURCE_VALUES_H
 
+#include "util/Maybe.h"
 #include "Resource.h"
 #include "StringPool.h"
 
@@ -27,9 +28,7 @@
 
 namespace aapt {
 
-struct ValueVisitor;
-struct ConstValueVisitor;
-struct ValueVisitorArgs;
+struct RawValueVisitor;
 
 /**
  * A resource value. This is an all-encompassing representation
@@ -39,13 +38,15 @@
  * but it is the simplest strategy.
  */
 struct Value {
+	virtual ~Value() = default;
+
     /**
      * Whether or not this is an Item.
      */
     virtual bool isItem() const;
 
     /**
-     * Whether this value is weak and can be overriden without
+     * Whether this value is weak and can be overridden without
      * warning or error. Default for base class is false.
      */
     virtual bool isWeak() const;
@@ -53,12 +54,7 @@
     /**
      * Calls the appropriate overload of ValueVisitor.
      */
-    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0;
-
-    /**
-     * Const version of accept().
-     */
-    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 0;
+    virtual void accept(RawValueVisitor* visitor) = 0;
 
     /**
      * Clone the value.
@@ -68,7 +64,7 @@
     /**
      * Human readable printout of this value.
      */
-    virtual void print(std::ostream& out) const = 0;
+    virtual void print(std::ostream* out) const = 0;
 };
 
 /**
@@ -76,8 +72,7 @@
  */
 template <typename Derived>
 struct BaseValue : public Value {
-    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
-    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+    void accept(RawValueVisitor* visitor) override;
 };
 
 /**
@@ -96,9 +91,9 @@
 
     /**
      * Fills in an android::Res_value structure with this Item's binary representation.
-     * Returns false if an error ocurred.
+     * Returns false if an error occurred.
      */
-    virtual bool flatten(android::Res_value& outValue) const = 0;
+    virtual bool flatten(android::Res_value* outValue) const = 0;
 };
 
 /**
@@ -106,8 +101,7 @@
  */
 template <typename Derived>
 struct BaseItem : public Item {
-    virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
-    virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
+    void accept(RawValueVisitor* visitor) override;
 };
 
 /**
@@ -122,8 +116,8 @@
         kAttribute,
     };
 
-    ResourceName name;
-    ResourceId id;
+    Maybe<ResourceName> name;
+    Maybe<ResourceId> id;
     Reference::Type referenceType;
     bool privateReference = false;
 
@@ -131,9 +125,9 @@
     Reference(const ResourceNameRef& n, Type type = Type::kResource);
     Reference(const ResourceId& i, Type type = Type::kResource);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     Reference* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
@@ -141,9 +135,9 @@
  */
 struct Id : public BaseItem<Id> {
     bool isWeak() const override;
-    bool flatten(android::Res_value& out) const override;
+    bool flatten(android::Res_value* out) const override;
     Id* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
@@ -156,9 +150,9 @@
 
     RawString(const StringPool::Ref& ref);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     RawString* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct String : public BaseItem<String> {
@@ -166,9 +160,9 @@
 
     String(const StringPool::Ref& ref);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     String* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct StyledString : public BaseItem<StyledString> {
@@ -176,9 +170,9 @@
 
     StyledString(const StringPool::StyleRef& ref);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     StyledString* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct FileReference : public BaseItem<FileReference> {
@@ -187,9 +181,9 @@
     FileReference() = default;
     FileReference(const StringPool::Ref& path);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     FileReference* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
@@ -200,10 +194,11 @@
 
     BinaryPrimitive() = default;
     BinaryPrimitive(const android::Res_value& val);
+    BinaryPrimitive(uint8_t dataType, uint32_t data);
 
-    bool flatten(android::Res_value& outValue) const override;
+    bool flatten(android::Res_value* outValue) const override;
     BinaryPrimitive* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Attribute : public BaseValue<Attribute> {
@@ -212,7 +207,7 @@
         uint32_t value;
     };
 
-    bool weak;
+	bool weak;
     uint32_t typeMask;
     uint32_t minInt;
     uint32_t maxInt;
@@ -221,9 +216,9 @@
     Attribute(bool w, uint32_t t = 0u);
 
     bool isWeak() const override;
-    virtual Attribute* clone(StringPool* newPool) const override;
-    void printMask(std::ostream& out) const;
-    virtual void print(std::ostream& out) const override;
+    Attribute* clone(StringPool* newPool) const override;
+    void printMask(std::ostream* out) const;
+    void print(std::ostream* out) const override;
 };
 
 struct Style : public BaseValue<Style> {
@@ -232,7 +227,7 @@
         std::unique_ptr<Item> value;
     };
 
-    Reference parent;
+    Maybe<Reference> parent;
 
     /**
      * If set to true, the parent was auto inferred from the
@@ -243,14 +238,14 @@
     std::vector<Entry> entries;
 
     Style* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Array : public BaseValue<Array> {
     std::vector<std::unique_ptr<Item>> items;
 
     Array* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Plural : public BaseValue<Plural> {
@@ -267,180 +262,31 @@
     std::array<std::unique_ptr<Item>, Count> values;
 
     Plural* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 struct Styleable : public BaseValue<Styleable> {
     std::vector<Reference> entries;
 
     Styleable* clone(StringPool* newPool) const override;
-    void print(std::ostream& out) const override;
+    void print(std::ostream* out) const override;
 };
 
 /**
  * Stream operator for printing Value objects.
  */
 inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
-    value.print(out);
+    value.print(&out);
     return out;
 }
 
 inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
-    return out << s.symbol.name.entry << "=" << s.value;
-}
-
-/**
- * The argument object that gets passed through the value
- * back to the ValueVisitor. Subclasses of ValueVisitor should
- * subclass ValueVisitorArgs to contain the data they need
- * to operate.
- */
-struct ValueVisitorArgs {};
-
-/**
- * Visits a value and runs the appropriate method based on its type.
- */
-struct ValueVisitor {
-    virtual void visit(Reference& reference, ValueVisitorArgs& args) {
-        visitItem(reference, args);
+    if (s.symbol.name) {
+        out << s.symbol.name.value().entry;
+    } else {
+        out << "???";
     }
-
-    virtual void visit(RawString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(String& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(StyledString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(FileReference& file, ValueVisitorArgs& args) {
-        visitItem(file, args);
-    }
-
-    virtual void visit(Id& id, ValueVisitorArgs& args) {
-        visitItem(id, args);
-    }
-
-    virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) {
-        visitItem(primitive, args);
-    }
-
-    virtual void visit(Attribute& attr, ValueVisitorArgs& args) {}
-    virtual void visit(Style& style, ValueVisitorArgs& args) {}
-    virtual void visit(Array& array, ValueVisitorArgs& args) {}
-    virtual void visit(Plural& array, ValueVisitorArgs& args) {}
-    virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {}
-
-    virtual void visitItem(Item& item, ValueVisitorArgs& args) {}
-};
-
-/**
- * Const version of ValueVisitor.
- */
-struct ConstValueVisitor {
-    virtual void visit(const Reference& reference, ValueVisitorArgs& args) {
-        visitItem(reference, args);
-    }
-
-    virtual void visit(const RawString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(const String& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(const StyledString& string, ValueVisitorArgs& args) {
-        visitItem(string, args);
-    }
-
-    virtual void visit(const FileReference& file, ValueVisitorArgs& args) {
-        visitItem(file, args);
-    }
-
-    virtual void visit(const Id& id, ValueVisitorArgs& args) {
-        visitItem(id, args);
-    }
-
-    virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) {
-        visitItem(primitive, args);
-    }
-
-    virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {}
-    virtual void visit(const Style& style, ValueVisitorArgs& args) {}
-    virtual void visit(const Array& array, ValueVisitorArgs& args) {}
-    virtual void visit(const Plural& array, ValueVisitorArgs& args) {}
-    virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {}
-
-    virtual void visitItem(const Item& item, ValueVisitorArgs& args) {}
-};
-
-/**
- * Convenience Visitor that forwards a specific type to a function.
- * Args are not used as the function can bind variables. Do not use
- * directly, use the wrapper visitFunc() method.
- */
-template <typename T, typename TFunc>
-struct ValueVisitorFunc : ValueVisitor {
-    TFunc func;
-
-    ValueVisitorFunc(TFunc f) : func(f) {
-    }
-
-    void visit(T& value, ValueVisitorArgs&) override {
-        func(value);
-    }
-};
-
-/**
- * Const version of ValueVisitorFunc.
- */
-template <typename T, typename TFunc>
-struct ConstValueVisitorFunc : ConstValueVisitor {
-    TFunc func;
-
-    ConstValueVisitorFunc(TFunc f) : func(f) {
-    }
-
-    void visit(const T& value, ValueVisitorArgs&) override {
-        func(value);
-    }
-};
-
-template <typename T, typename TFunc>
-void visitFunc(Value& value, TFunc f) {
-    ValueVisitorFunc<T, TFunc> visitor(f);
-    value.accept(visitor, ValueVisitorArgs{});
-}
-
-template <typename T, typename TFunc>
-void visitFunc(const Value& value, TFunc f) {
-    ConstValueVisitorFunc<T, TFunc> visitor(f);
-    value.accept(visitor, ValueVisitorArgs{});
-}
-
-template <typename Derived>
-void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
-    visitor.visit(static_cast<Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
-    visitor.visit(static_cast<const Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
-    visitor.visit(static_cast<Derived&>(*this), args);
-}
-
-template <typename Derived>
-void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
-    visitor.visit(static_cast<const Derived&>(*this), args);
+    return out << "=" << s.value;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp
deleted file mode 100644
index 48da93e..0000000
--- a/tools/aapt2/ScopedXmlPullParser.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ScopedXmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) :
-        mParser(parser), mDepth(parser->getDepth()), mDone(false) {
-}
-
-ScopedXmlPullParser::~ScopedXmlPullParser() {
-    while (isGoodEvent(next()));
-}
-
-XmlPullParser::Event ScopedXmlPullParser::next() {
-    if (mDone) {
-        return Event::kEndDocument;
-    }
-
-    const Event event = mParser->next();
-    if (mParser->getDepth() <= mDepth) {
-        mDone = true;
-    }
-    return event;
-}
-
-XmlPullParser::Event ScopedXmlPullParser::getEvent() const {
-    return mParser->getEvent();
-}
-
-const std::string& ScopedXmlPullParser::getLastError() const {
-    return mParser->getLastError();
-}
-
-const std::u16string& ScopedXmlPullParser::getComment() const {
-    return mParser->getComment();
-}
-
-size_t ScopedXmlPullParser::getLineNumber() const {
-    return mParser->getLineNumber();
-}
-
-size_t ScopedXmlPullParser::getDepth() const {
-    const size_t depth = mParser->getDepth();
-    if (depth < mDepth) {
-        return 0;
-    }
-    return depth - mDepth;
-}
-
-const std::u16string& ScopedXmlPullParser::getText() const {
-    return mParser->getText();
-}
-
-const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const {
-    return mParser->getNamespacePrefix();
-}
-
-const std::u16string& ScopedXmlPullParser::getNamespaceUri() const {
-    return mParser->getNamespaceUri();
-}
-
-bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package,
-                                            const std::u16string& defaultPackage) const {
-    return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& ScopedXmlPullParser::getElementNamespace() const {
-    return mParser->getElementNamespace();
-}
-
-const std::u16string& ScopedXmlPullParser::getElementName() const {
-    return mParser->getElementName();
-}
-
-size_t ScopedXmlPullParser::getAttributeCount() const {
-    return mParser->getAttributeCount();
-}
-
-XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const {
-    return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const {
-    return mParser->endAttributes();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ScopedXmlPullParser.h b/tools/aapt2/ScopedXmlPullParser.h
deleted file mode 100644
index a040f60..0000000
--- a/tools/aapt2/ScopedXmlPullParser.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_SCOPED_XML_PULL_PARSER_H
-#define AAPT_SCOPED_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-/**
- * An XmlPullParser that will not read past the depth
- * of the underlying parser. When this parser is destroyed,
- * it moves the underlying parser to the same depth it
- * started with.
- *
- * You can write code like this:
- *
- *   while (XmlPullParser::isGoodEvent(parser.next())) {
- *     if (parser.getEvent() != XmlPullParser::Event::StartElement) {
- *       continue;
- *     }
- *
- *     ScopedXmlPullParser scoped(parser);
- *     if (parser.getElementName() == u"id") {
- *       // do work.
- *     } else {
- *       // do nothing, as all the sub elements will be skipped
- *       // when scoped goes out of scope.
- *     }
- *   }
- */
-class ScopedXmlPullParser : public XmlPullParser {
-public:
-    ScopedXmlPullParser(XmlPullParser* parser);
-    ScopedXmlPullParser(const ScopedXmlPullParser&) = delete;
-    ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete;
-    ~ScopedXmlPullParser();
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override;
-    Event next() override;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
-            const override;
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-private:
-    XmlPullParser* mParser;
-    size_t mDepth;
-    bool mDone;
-};
-
-} // namespace aapt
-
-#endif // AAPT_SCOPED_XML_PULL_PARSER_H
diff --git a/tools/aapt2/ScopedXmlPullParser_test.cpp b/tools/aapt2/ScopedXmlPullParser_test.cpp
deleted file mode 100644
index 342f305..0000000
--- a/tools/aapt2/ScopedXmlPullParser_test.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ScopedXmlPullParser.h"
-#include "SourceXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources><string></string></resources>" << std::endl;
-
-    SourceXmlPullParser sourceParser(input);
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-    {
-        ScopedXmlPullParser scopedParser(&sourceParser);
-        EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next());
-        EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-        EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next());
-    }
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources><string></string></resources>" << std::endl;
-
-    SourceXmlPullParser sourceParser(input);
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-    {
-        ScopedXmlPullParser scopedParser(&sourceParser);
-        EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-    }
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources><string><foo></foo></string></resources>" << std::endl;
-
-    SourceXmlPullParser sourceParser(input);
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
-
-    {
-        ScopedXmlPullParser scopedParser(&sourceParser);
-        EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName());
-        while (XmlPullParser::isGoodEvent(scopedParser.next())) {
-            if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) {
-                continue;
-            }
-
-            ScopedXmlPullParser subScopedParser(&scopedParser);
-            EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName());
-        }
-    }
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
-    EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 9bdae49..c2a22bf 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -34,8 +34,9 @@
     { 0x02bd, SDK_FROYO },
     { 0x02cb, SDK_GINGERBREAD },
     { 0x0361, SDK_HONEYCOMB },
-    { 0x0366, SDK_HONEYCOMB_MR1 },
-    { 0x03a6, SDK_HONEYCOMB_MR2 },
+    { 0x0363, SDK_HONEYCOMB_MR1 },
+    { 0x0366, SDK_HONEYCOMB_MR2 },
+    { 0x03a6, SDK_ICE_CREAM_SANDWICH },
     { 0x03ae, SDK_JELLY_BEAN },
     { 0x03cc, SDK_JELLY_BEAN_MR1 },
     { 0x03da, SDK_JELLY_BEAN_MR2 },
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 3606488..8af203c 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -17,72 +17,58 @@
 #ifndef AAPT_SOURCE_H
 #define AAPT_SOURCE_H
 
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
 #include <ostream>
 #include <string>
-#include <tuple>
 
 namespace aapt {
 
-struct SourceLineColumn;
-struct SourceLine;
-
 /**
  * Represents a file on disk. Used for logging and
  * showing errors.
  */
 struct Source {
     std::string path;
+    Maybe<size_t> line;
 
-    inline SourceLine line(size_t line) const;
-};
+    Source() = default;
 
-/**
- * Represents a file on disk and a line number in that file.
- * Used for logging and showing errors.
- */
-struct SourceLine {
-    std::string path;
-    size_t line;
+    inline Source(const StringPiece& path) : path(path.toString()) {
+    }
 
-    inline SourceLineColumn column(size_t column) const;
-};
+    inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) {
+    }
 
-/**
- * Represents a file on disk and a line:column number in that file.
- * Used for logging and showing errors.
- */
-struct SourceLineColumn {
-    std::string path;
-    size_t line;
-    size_t column;
+    inline Source withLine(size_t line) const {
+        return Source(path, line);
+    }
 };
 
 //
 // Implementations
 //
 
-SourceLine Source::line(size_t line) const {
-    return SourceLine{ path, line };
-}
-
-SourceLineColumn SourceLine::column(size_t column) const {
-    return SourceLineColumn{ path, line, column };
-}
-
 inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
-    return out << source.path;
+    out << source.path;
+    if (source.line) {
+        out << ":" << source.line.value();
+    }
+    return out;
 }
 
-inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) {
-    return out << source.path << ":" << source.line;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) {
-    return out << source.path << ":" << source.line << ":" << source.column;
-}
-
-inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) {
-    return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line);
+inline bool operator<(const Source& lhs, const Source& rhs) {
+    int cmp = lhs.path.compare(rhs.path);
+    if (cmp < 0) return true;
+    if (cmp > 0) return false;
+    if (lhs.line) {
+        if (rhs.line) {
+            return lhs.line.value() < rhs.line.value();
+        }
+        return false;
+    }
+    return bool(rhs.line);
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h
deleted file mode 100644
index d8ed459..0000000
--- a/tools/aapt2/SourceXmlPullParser.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_SOURCE_XML_PULL_PARSER_H
-#define AAPT_SOURCE_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <istream>
-#include <expat.h>
-#include <queue>
-#include <stack>
-#include <string>
-#include <vector>
-
-namespace aapt {
-
-class SourceXmlPullParser : public XmlPullParser {
-public:
-    SourceXmlPullParser(std::istream& in);
-    SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete;
-    ~SourceXmlPullParser();
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override ;
-    Event next() override ;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package,
-                           const std::u16string& defaultPackage) const override;
-
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-private:
-    static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
-    static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
-    static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
-    static void XMLCALL endElementHandler(void* userData, const char* name);
-    static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
-    static void XMLCALL commentDataHandler(void* userData, const char* comment);
-
-    struct EventData {
-        Event event;
-        size_t lineNumber;
-        size_t depth;
-        std::u16string data1;
-        std::u16string data2;
-        std::u16string comment;
-        std::vector<Attribute> attributes;
-    };
-
-    std::istream& mIn;
-    XML_Parser mParser;
-    char mBuffer[16384];
-    std::queue<EventData> mEventQueue;
-    std::string mLastError;
-    const std::u16string mEmpty;
-    size_t mDepth;
-    std::stack<std::u16string> mNamespaceUris;
-    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
-};
-
-} // namespace aapt
-
-#endif // AAPT_SOURCE_XML_PULL_PARSER_H
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index c19aa98..8552f47 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
-#include "StringPiece.h"
+#include "util/BigBuffer.h"
+#include "util/StringPiece.h"
 #include "StringPool.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <androidfw/ResourceTypes.h>
@@ -219,7 +219,7 @@
     auto indexIter = std::begin(mIndexedStrings);
     while (indexIter != iterEnd) {
         if (indexIter->second->ref <= 0) {
-            mIndexedStrings.erase(indexIter++);
+            indexIter = mIndexedStrings.erase(indexIter);
         } else {
             ++indexIter;
         }
@@ -241,6 +241,12 @@
     // a deleted string from the StyleEntry.
     mStrings.erase(endIter2, std::end(mStrings));
     mStyles.erase(endIter3, std::end(mStyles));
+
+    // Reassign the indices.
+    const size_t len = mStrings.size();
+    for (size_t index = 0; index < len; index++) {
+        mStrings[index]->index = index;
+    }
 }
 
 void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 14304a6..509e304 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -17,9 +17,9 @@
 #ifndef AAPT_STRING_POOL_H
 #define AAPT_STRING_POOL_H
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
 #include "ConfigDescription.h"
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 #include <functional>
 #include <map>
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 9552937..c722fbe 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "StringPool.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <gtest/gtest.h>
 #include <string>
@@ -67,15 +67,23 @@
 TEST(StringPoolTest, PruneStringsWithNoReferences) {
     StringPool pool;
 
+    StringPool::Ref refA = pool.makeRef(u"foo");
     {
         StringPool::Ref ref = pool.makeRef(u"wut");
         EXPECT_EQ(*ref, u"wut");
-        EXPECT_EQ(1u, pool.size());
+        EXPECT_EQ(2u, pool.size());
     }
+    StringPool::Ref refB = pool.makeRef(u"bar");
 
-    EXPECT_EQ(1u, pool.size());
+    EXPECT_EQ(3u, pool.size());
     pool.prune();
-    EXPECT_EQ(0u, pool.size());
+    EXPECT_EQ(2u, pool.size());
+    StringPool::const_iterator iter = begin(pool);
+    EXPECT_EQ((*iter)->value, u"foo");
+    EXPECT_LT((*iter)->index, 2u);
+    ++iter;
+    EXPECT_EQ((*iter)->value, u"bar");
+    EXPECT_LT((*iter)->index, 2u);
 }
 
 TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp
deleted file mode 100644
index b7c04f0..0000000
--- a/tools/aapt2/TableFlattener.cpp
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BigBuffer.h"
-#include "ConfigDescription.h"
-#include "Logger.h"
-#include "ResourceTable.h"
-#include "ResourceTypeExtensions.h"
-#include "ResourceValues.h"
-#include "StringPool.h"
-#include "TableFlattener.h"
-#include "Util.h"
-
-#include <algorithm>
-#include <androidfw/ResourceTypes.h>
-#include <sstream>
-
-namespace aapt {
-
-struct FlatEntry {
-    const ResourceEntry* entry;
-    const Value* value;
-    uint32_t entryKey;
-    uint32_t sourcePathKey;
-    uint32_t sourceLine;
-};
-
-/**
- * Visitor that knows how to encode Map values.
- */
-class MapFlattener : public ConstValueVisitor {
-public:
-    MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) :
-            mOut(out), mSymbols(symbols) {
-        mMap = mOut->nextBlock<android::ResTable_map_entry>();
-        mMap->key.index = flatEntry.entryKey;
-        mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
-        if (flatEntry.entry->publicStatus.isPublic) {
-            mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
-        }
-        if (flatEntry.value->isWeak()) {
-            mMap->flags |= android::ResTable_entry::FLAG_WEAK;
-        }
-
-        ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
-        sourceBlock->pathIndex = flatEntry.sourcePathKey;
-        sourceBlock->line = flatEntry.sourceLine;
-
-        mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
-    }
-
-    void flattenParent(const Reference& ref) {
-        if (!ref.id.isValid()) {
-            mSymbols->push_back({
-                    ResourceNameRef(ref.name),
-                    (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
-            });
-        }
-        mMap->parent.ident = ref.id.id;
-    }
-
-    void flattenEntry(const Reference& key, const Item& value) {
-        mMap->count++;
-
-        android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
-
-        // Write the key.
-        if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
-            assert(!key.name.entry.empty());
-            mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
-                    mOut->size() - sizeof(*outMapEntry)));
-        }
-        outMapEntry->name.ident = key.id.id;
-
-        // Write the value.
-        value.flatten(outMapEntry->value);
-
-        if (outMapEntry->value.data == 0x0) {
-            visitFunc<Reference>(value, [&](const Reference& reference) {
-                mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
-                        mOut->size() - sizeof(outMapEntry->value.data)));
-            });
-        }
-        outMapEntry->value.size = sizeof(outMapEntry->value);
-    }
-
-    void flattenValueOnly(const Item& value) {
-        mMap->count++;
-
-        android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
-
-        // Write the value.
-        value.flatten(outMapEntry->value);
-
-        if (outMapEntry->value.data == 0x0) {
-            visitFunc<Reference>(value, [&](const Reference& reference) {
-                mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
-                        mOut->size() - sizeof(outMapEntry->value.data)));
-            });
-        }
-        outMapEntry->value.size = sizeof(outMapEntry->value);
-    }
-
-    static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
-        return lhs->key.id < rhs->key.id;
-    }
-
-    void visit(const Style& style, ValueVisitorArgs&) override {
-        if (style.parent.name.isValid()) {
-            flattenParent(style.parent);
-        }
-
-        // First sort the entries by ID.
-        std::vector<const Style::Entry*> sortedEntries;
-        for (const auto& styleEntry : style.entries) {
-            auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
-                    &styleEntry, compareStyleEntries);
-            sortedEntries.insert(iter, &styleEntry);
-        }
-
-        for (const Style::Entry* styleEntry : sortedEntries) {
-            flattenEntry(styleEntry->key, *styleEntry->value);
-        }
-    }
-
-    void visit(const Attribute& attr, ValueVisitorArgs&) override {
-        android::Res_value tempVal;
-        tempVal.dataType = android::Res_value::TYPE_INT_DEC;
-        tempVal.data = attr.typeMask;
-        flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
-                BinaryPrimitive(tempVal));
-
-        for (const auto& symbol : attr.symbols) {
-            tempVal.data = symbol.value;
-            flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
-        }
-    }
-
-    void visit(const Styleable& styleable, ValueVisitorArgs&) override {
-        for (const auto& attr : styleable.entries) {
-            flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
-        }
-    }
-
-    void visit(const Array& array, ValueVisitorArgs&) override {
-        for (const auto& item : array.items) {
-            flattenValueOnly(*item);
-        }
-    }
-
-    void visit(const Plural& plural, ValueVisitorArgs&) override {
-        const size_t count = plural.values.size();
-        for (size_t i = 0; i < count; i++) {
-            if (!plural.values[i]) {
-                continue;
-            }
-
-            ResourceId q;
-            switch (i) {
-                case Plural::Zero:
-                    q.id = android::ResTable_map::ATTR_ZERO;
-                    break;
-
-                case Plural::One:
-                    q.id = android::ResTable_map::ATTR_ONE;
-                    break;
-
-                case Plural::Two:
-                    q.id = android::ResTable_map::ATTR_TWO;
-                    break;
-
-                case Plural::Few:
-                    q.id = android::ResTable_map::ATTR_FEW;
-                    break;
-
-                case Plural::Many:
-                    q.id = android::ResTable_map::ATTR_MANY;
-                    break;
-
-                case Plural::Other:
-                    q.id = android::ResTable_map::ATTR_OTHER;
-                    break;
-
-                default:
-                    assert(false);
-                    break;
-            }
-
-            flattenEntry(Reference(q), *plural.values[i]);
-        }
-    }
-
-private:
-    BigBuffer* mOut;
-    SymbolEntryVector* mSymbols;
-    android::ResTable_map_entry* mMap;
-};
-
-/**
- * Flattens a value, with special handling for References.
- */
-struct ValueFlattener : ConstValueVisitor {
-    ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
-            result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
-        mOutValue = mOut->nextBlock<android::Res_value>();
-    }
-
-    virtual void visit(const Reference& ref, ValueVisitorArgs& a) override {
-        visitItem(ref, a);
-        if (mOutValue->data == 0x0) {
-            mSymbols->push_back({
-                    ResourceNameRef(ref.name),
-                    mOut->size() - sizeof(mOutValue->data)});
-        }
-    }
-
-    virtual void visitItem(const Item& item, ValueVisitorArgs&) override {
-        result = item.flatten(*mOutValue);
-        mOutValue->res0 = 0;
-        mOutValue->size = sizeof(*mOutValue);
-    }
-
-    bool result;
-
-private:
-    BigBuffer* mOut;
-    android::Res_value* mOutValue;
-    SymbolEntryVector* mSymbols;
-};
-
-TableFlattener::TableFlattener(Options options)
-: mOptions(options) {
-}
-
-bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
-                                  SymbolEntryVector* symbols) {
-    if (flatEntry.value->isItem()) {
-        android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
-
-        if (flatEntry.entry->publicStatus.isPublic) {
-            entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
-        }
-
-        if (flatEntry.value->isWeak()) {
-            entry->flags |= android::ResTable_entry::FLAG_WEAK;
-        }
-
-        entry->key.index = flatEntry.entryKey;
-        entry->size = sizeof(*entry);
-
-        if (mOptions.useExtendedChunks) {
-            // Write the extra source block. This will be ignored by
-            // the Android runtime.
-            ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
-            sourceBlock->pathIndex = flatEntry.sourcePathKey;
-            sourceBlock->line = flatEntry.sourceLine;
-            entry->size += sizeof(*sourceBlock);
-        }
-
-        const Item* item = static_cast<const Item*>(flatEntry.value);
-        ValueFlattener flattener(out, symbols);
-        item->accept(flattener, {});
-        return flattener.result;
-    }
-
-    MapFlattener flattener(out, flatEntry, symbols);
-    flatEntry.value->accept(flattener, {});
-    return true;
-}
-
-bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
-    const size_t beginning = out->size();
-
-    if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
-        Logger::error()
-                << "ResourceTable has no package ID set."
-                << std::endl;
-        return false;
-    }
-
-    SymbolEntryVector symbolEntries;
-
-    StringPool typePool;
-    StringPool keyPool;
-    StringPool sourcePool;
-
-    // Sort the types by their IDs. They will be inserted into the StringPool
-    // in this order.
-    std::vector<ResourceTableType*> sortedTypes;
-    for (const auto& type : table) {
-        if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
-            continue;
-        }
-
-        auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
-                [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
-                    return lhs->typeId < rhs->typeId;
-                });
-        sortedTypes.insert(iter, type.get());
-    }
-
-    BigBuffer typeBlock(1024);
-    size_t expectedTypeId = 1;
-    for (const ResourceTableType* type : sortedTypes) {
-        if (type->typeId == ResourceTableType::kUnsetTypeId
-                || type->typeId == 0) {
-            Logger::error()
-                    << "resource type '"
-                    << type->type
-                    << "' from package '"
-                    << table.getPackage()
-                    << "' has no ID."
-                    << std::endl;
-            return false;
-        }
-
-        // If there is a gap in the type IDs, fill in the StringPool
-        // with empty values until we reach the ID we expect.
-        while (type->typeId > expectedTypeId) {
-            std::u16string typeName(u"?");
-            typeName += expectedTypeId;
-            typePool.makeRef(typeName);
-            expectedTypeId++;
-        }
-        expectedTypeId++;
-        typePool.makeRef(toString(type->type));
-
-        android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
-        spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
-        spec->header.headerSize = sizeof(*spec);
-        spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
-        spec->id = type->typeId;
-        spec->entryCount = type->entries.size();
-
-        if (type->entries.empty()) {
-            continue;
-        }
-
-        // Reserve space for the masks of each resource in this type. These
-        // show for which configuration axis the resource changes.
-        uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
-
-        // Sort the entries by entry ID and write their configuration masks.
-        std::vector<ResourceEntry*> entries;
-        const size_t entryCount = type->entries.size();
-        for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
-            const auto& entry = type->entries[entryIndex];
-
-            if (entry->entryId == ResourceEntry::kUnsetEntryId) {
-                Logger::error()
-                        << "resource '"
-                        << ResourceName{ table.getPackage(), type->type, entry->name }
-                        << "' has no ID."
-                        << std::endl;
-                return false;
-            }
-
-            auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
-                    [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
-                        return lhs->entryId < rhs->entryId;
-                    });
-            entries.insert(iter, entry.get());
-
-            // Populate the config masks for this entry.
-            if (entry->publicStatus.isPublic) {
-                configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
-            }
-
-            const size_t configCount = entry->values.size();
-            for (size_t i = 0; i < configCount; i++) {
-                const ConfigDescription& config = entry->values[i].config;
-                for (size_t j = i + 1; j < configCount; j++) {
-                    configMasks[entry->entryId] |= config.diff(entry->values[j].config);
-                }
-            }
-        }
-
-        const size_t beforePublicHeader = typeBlock.size();
-        Public_header* publicHeader = nullptr;
-        if (mOptions.useExtendedChunks) {
-            publicHeader = typeBlock.nextBlock<Public_header>();
-            publicHeader->header.type = RES_TABLE_PUBLIC_TYPE;
-            publicHeader->header.headerSize = sizeof(*publicHeader);
-            publicHeader->typeId = type->typeId;
-        }
-
-        // The binary resource table lists resource entries for each configuration.
-        // We store them inverted, where a resource entry lists the values for each
-        // configuration available. Here we reverse this to match the binary table.
-        std::map<ConfigDescription, std::vector<FlatEntry>> data;
-        for (const ResourceEntry* entry : entries) {
-            size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
-
-            if (keyIndex > std::numeric_limits<uint32_t>::max()) {
-                Logger::error()
-                        << "resource key string pool exceeded max size."
-                        << std::endl;
-                return false;
-            }
-
-            if (publicHeader && entry->publicStatus.isPublic) {
-                // Write the public status of this entry.
-                Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>();
-                publicEntry->entryId = static_cast<uint32_t>(entry->entryId);
-                publicEntry->key.index = static_cast<uint32_t>(keyIndex);
-                publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef(
-                            util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
-                publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line);
-                publicHeader->count += 1;
-            }
-
-            for (const auto& configValue : entry->values) {
-                data[configValue.config].push_back(FlatEntry{
-                        entry,
-                        configValue.value.get(),
-                        static_cast<uint32_t>(keyIndex),
-                        static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
-                                    configValue.source.path)).getIndex()),
-                        static_cast<uint32_t>(configValue.source.line)
-                });
-            }
-        }
-
-        if (publicHeader) {
-            typeBlock.align4();
-            publicHeader->header.size =
-                    static_cast<uint32_t>(typeBlock.size() - beforePublicHeader);
-        }
-
-        // Begin flattening a configuration for the current type.
-        for (const auto& entry : data) {
-            const size_t typeHeaderStart = typeBlock.size();
-            android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
-            typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
-            typeHeader->header.headerSize = sizeof(*typeHeader);
-            typeHeader->id = type->typeId;
-            typeHeader->entryCount = type->entries.size();
-            typeHeader->entriesStart = typeHeader->header.headerSize
-                    + (sizeof(uint32_t) * type->entries.size());
-            typeHeader->config = entry.first;
-
-            uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
-            memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
-
-            const size_t entryStart = typeBlock.size();
-            for (const FlatEntry& flatEntry : entry.second) {
-                assert(flatEntry.entry->entryId < type->entries.size());
-                indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart;
-                if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
-                    Logger::error()
-                            << "failed to flatten resource '"
-                            << ResourceNameRef {
-                                    table.getPackage(), type->type, flatEntry.entry->name }
-                            << "' for configuration '"
-                            << entry.first
-                            << "'."
-                            << std::endl;
-                    return false;
-                }
-            }
-
-            typeBlock.align4();
-            typeHeader->header.size = typeBlock.size() - typeHeaderStart;
-        }
-    }
-
-    const size_t beforeTable = out->size();
-    android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
-    header->header.type = android::RES_TABLE_TYPE;
-    header->header.headerSize = sizeof(*header);
-    header->packageCount = 1;
-
-    SymbolTable_entry* symbolEntryData = nullptr;
-    if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
-        const size_t beforeSymbolTable = out->size();
-        StringPool symbolPool;
-        SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
-        symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
-        symbolHeader->header.headerSize = sizeof(*symbolHeader);
-        symbolHeader->count = symbolEntries.size();
-
-        symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
-
-        size_t i = 0;
-        for (const auto& entry : symbolEntries) {
-            symbolEntryData[i].offset = entry.second;
-            StringPool::Ref ref = symbolPool.makeRef(
-                    entry.first.package.toString() + u":" +
-                    toString(entry.first.type).toString() + u"/" +
-                    entry.first.entry.toString());
-            symbolEntryData[i].stringIndex = ref.getIndex();
-            i++;
-        }
-
-        StringPool::flattenUtf8(out, symbolPool);
-        out->align4();
-        symbolHeader->header.size = out->size() - beforeSymbolTable;
-    }
-
-    if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
-        const size_t beforeSourcePool = out->size();
-        android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
-        sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
-        sourceHeader->headerSize = sizeof(*sourceHeader);
-        StringPool::flattenUtf8(out, sourcePool);
-        out->align4();
-        sourceHeader->size = out->size() - beforeSourcePool;
-    }
-
-    StringPool::flattenUtf8(out, table.getValueStringPool());
-
-    const size_t beforePackageIndex = out->size();
-    android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
-    package->header.type = android::RES_TABLE_PACKAGE_TYPE;
-    package->header.headerSize = sizeof(*package);
-
-    if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
-        Logger::error()
-                << "package ID 0x'"
-                << std::hex << table.getPackageId() << std::dec
-                << "' is invalid."
-                << std::endl;
-        return false;
-    }
-    package->id = table.getPackageId();
-
-    if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
-        Logger::error()
-                << "package name '"
-                << table.getPackage()
-                << "' is too long."
-                << std::endl;
-        return false;
-    }
-    memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
-            table.getPackage().length() * sizeof(char16_t));
-    package->name[table.getPackage().length()] = 0;
-
-    package->typeStrings = package->header.headerSize;
-    StringPool::flattenUtf16(out, typePool);
-    package->keyStrings = out->size() - beforePackageIndex;
-    StringPool::flattenUtf16(out, keyPool);
-
-    if (symbolEntryData != nullptr) {
-        for (size_t i = 0; i < symbolEntries.size(); i++) {
-            symbolEntryData[i].offset += out->size() - beginning;
-        }
-    }
-
-    out->appendBuffer(std::move(typeBlock));
-
-    package->header.size = out->size() - beforePackageIndex;
-    header->header.size = out->size() - beforeTable;
-    return true;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/TableFlattener.h b/tools/aapt2/TableFlattener.h
deleted file mode 100644
index ccbb737..0000000
--- a/tools/aapt2/TableFlattener.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_TABLE_FLATTENER_H
-#define AAPT_TABLE_FLATTENER_H
-
-#include "BigBuffer.h"
-#include "ResourceTable.h"
-
-namespace aapt {
-
-using SymbolEntryVector = std::vector<std::pair<ResourceNameRef, uint32_t>>;
-
-struct FlatEntry;
-
-/**
- * Flattens a ResourceTable into a binary format suitable
- * for loading into a ResTable on the host or device.
- */
-struct TableFlattener {
-    /**
-     * A set of options for this TableFlattener.
-     */
-    struct Options {
-        /**
-         * Specifies whether to output extended chunks, like
-         * source information and mising symbol entries. Default
-         * is true.
-         *
-         * Set this to false when emitting the final table to be used
-         * on device.
-         */
-        bool useExtendedChunks = true;
-    };
-
-    TableFlattener(Options options);
-
-    bool flatten(BigBuffer* out, const ResourceTable& table);
-
-private:
-    bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols);
-
-    Options mOptions;
-};
-
-} // namespace aapt
-
-#endif // AAPT_TABLE_FLATTENER_H
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
deleted file mode 100644
index 92f2a1c..0000000
--- a/tools/aapt2/Util_test.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include <string>
-
-#include "StringPiece.h"
-#include "Util.h"
-
-namespace aapt {
-
-TEST(UtilTest, TrimOnlyWhitespace) {
-    const std::u16string full = u"\n        ";
-
-    StringPiece16 trimmed = util::trimWhitespace(full);
-    EXPECT_TRUE(trimmed.empty());
-    EXPECT_EQ(0u, trimmed.size());
-}
-
-TEST(UtilTest, StringEndsWith) {
-    EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
-}
-
-TEST(UtilTest, StringStartsWith) {
-    EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
-}
-
-TEST(UtilTest, StringBuilderSplitEscapeSequence) {
-    EXPECT_EQ(StringPiece16(u"this is a new\nline."),
-            util::StringBuilder().append(u"this is a new\\")
-                                 .append(u"nline.")
-                                 .str());
-}
-
-TEST(UtilTest, StringBuilderWhitespaceRemoval) {
-    EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
-            util::StringBuilder().append(u"    hey guys ")
-                                 .append(u" this is so cool ")
-                                 .str());
-
-    EXPECT_EQ(StringPiece16(u" wow,  so many \t spaces. what?"),
-            util::StringBuilder().append(u" \" wow,  so many \t ")
-                                 .append(u"spaces. \"what? ")
-                                 .str());
-
-    EXPECT_EQ(StringPiece16(u"where is the pie?"),
-            util::StringBuilder().append(u"  where \t ")
-                                 .append(u" \nis the "" pie?")
-                                 .str());
-}
-
-TEST(UtilTest, StringBuilderEscaping) {
-    EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
-            util::StringBuilder().append(u"    hey guys\\n ")
-                                 .append(u" this \\t is so\\\\ cool ")
-                                 .str());
-
-    EXPECT_EQ(StringPiece16(u"@?#\\\'"),
-            util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
-                                 .str());
-}
-
-TEST(UtilTest, StringBuilderMisplacedQuote) {
-    util::StringBuilder builder{};
-    EXPECT_FALSE(builder.append(u"they're coming!"));
-}
-
-TEST(UtilTest, StringBuilderUnicodeCodes) {
-    EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
-            util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
-                                 .str());
-
-    EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
-}
-
-TEST(UtilTest, TokenizeInput) {
-    auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
-    auto iter = tokenizer.begin();
-    ASSERT_EQ(*iter, StringPiece16(u"this"));
-    ++iter;
-    ASSERT_EQ(*iter, StringPiece16(u" is"));
-    ++iter;
-    ASSERT_EQ(*iter, StringPiece16(u"the"));
-    ++iter;
-    ASSERT_EQ(*iter, StringPiece16(u"end"));
-    ++iter;
-    ASSERT_EQ(tokenizer.end(), iter);
-}
-
-TEST(UtilTest, IsJavaClassName) {
-    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
-    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
-    EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
-    EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
-    EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
-    EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
-    EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
-    EXPECT_FALSE(util::isJavaClassName(u"android"));
-}
-
-TEST(UtilTest, FullyQualifiedClassName) {
-    Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"android.asdf");
-
-    res = util::getFullyQualifiedClassName(u"android", u".asdf");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"android.asdf");
-
-    res = util::getFullyQualifiedClassName(u"android", u".a.b");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"android.a.b");
-
-    res = util::getFullyQualifiedClassName(u"android", u"a.b");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"a.b");
-
-    res = util::getFullyQualifiedClassName(u"", u"a.b");
-    ASSERT_TRUE(res);
-    EXPECT_EQ(res.value(), u"a.b");
-
-    res = util::getFullyQualifiedClassName(u"", u"");
-    ASSERT_FALSE(res);
-
-    res = util::getFullyQualifiedClassName(u"android", u"./Apple");
-    ASSERT_FALSE(res);
-}
-
-
-} // namespace aapt
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
new file mode 100644
index 0000000..ee058aa
--- /dev/null
+++ b/tools/aapt2/ValueVisitor.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_VALUE_VISITOR_H
+#define AAPT_VALUE_VISITOR_H
+
+#include "ResourceValues.h"
+
+namespace aapt {
+
+/**
+ * Visits a value and invokes the appropriate method based on its type. Does not traverse
+ * into compound types. Use ValueVisitor for that.
+ */
+struct RawValueVisitor {
+    virtual ~RawValueVisitor() = default;
+
+    virtual void visitItem(Item* value) {}
+    virtual void visit(Reference* value) { visitItem(value); }
+    virtual void visit(RawString* value) { visitItem(value); }
+    virtual void visit(String* value) { visitItem(value); }
+    virtual void visit(StyledString* value) { visitItem(value); }
+    virtual void visit(FileReference* value) { visitItem(value); }
+    virtual void visit(Id* value) { visitItem(value); }
+    virtual void visit(BinaryPrimitive* value) { visitItem(value); }
+
+    virtual void visit(Attribute* value) {}
+    virtual void visit(Style* value) {}
+    virtual void visit(Array* value) {}
+    virtual void visit(Plural* value) {}
+    virtual void visit(Styleable* value) {}
+};
+
+#define DECL_VISIT_COMPOUND_VALUE(T) \
+    virtual void visit(T* value) { \
+        visitSubValues(value); \
+    }
+
+/**
+ * Visits values, and if they are compound values, visits the components as well.
+ */
+struct ValueVisitor : public RawValueVisitor {
+    // The compiler will think we're hiding an overload, when we actually intend
+    // to call into RawValueVisitor. This will expose the visit methods in the super
+    // class so the compiler knows we are trying to call them.
+    using RawValueVisitor::visit;
+
+    void visitSubValues(Attribute* attribute) {
+        for (Attribute::Symbol& symbol : attribute->symbols) {
+            visit(&symbol.symbol);
+        }
+    }
+
+    void visitSubValues(Style* style) {
+        if (style->parent) {
+            visit(&style->parent.value());
+        }
+
+        for (Style::Entry& entry : style->entries) {
+            visit(&entry.key);
+            entry.value->accept(this);
+        }
+    }
+
+    void visitSubValues(Array* array) {
+        for (std::unique_ptr<Item>& item : array->items) {
+            item->accept(this);
+        }
+    }
+
+    void visitSubValues(Plural* plural) {
+        for (std::unique_ptr<Item>& item : plural->values) {
+            if (item) {
+                item->accept(this);
+            }
+        }
+    }
+
+    void visitSubValues(Styleable* styleable) {
+        for (Reference& reference : styleable->entries) {
+            visit(&reference);
+        }
+    }
+
+    DECL_VISIT_COMPOUND_VALUE(Attribute);
+    DECL_VISIT_COMPOUND_VALUE(Style);
+    DECL_VISIT_COMPOUND_VALUE(Array);
+    DECL_VISIT_COMPOUND_VALUE(Plural);
+    DECL_VISIT_COMPOUND_VALUE(Styleable);
+};
+
+/**
+ * Do not use directly. Helper struct for dyn_cast.
+ */
+template <typename T>
+struct DynCastVisitor : public RawValueVisitor {
+    T* value = nullptr;
+
+    void visit(T* v) override {
+        value = v;
+    }
+};
+
+/**
+ * Returns a valid pointer to T if the Value is of subtype T.
+ * Otherwise, returns nullptr.
+ */
+template <typename T>
+T* valueCast(Value* value) {
+    if (!value) {
+        return nullptr;
+    }
+    DynCastVisitor<T> visitor;
+    value->accept(&visitor);
+    return visitor.value;
+}
+
+} // namespace aapt
+
+#endif // AAPT_VALUE_VISITOR_H
diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp
new file mode 100644
index 0000000..1624079
--- /dev/null
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <string>
+
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
+#include "test/Builders.h"
+
+namespace aapt {
+
+struct SingleReferenceVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+
+    Reference* visited = nullptr;
+
+    void visit(Reference* ref) override {
+        visited = ref;
+    }
+};
+
+struct StyleVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+
+    std::list<Reference*> visitedRefs;
+    Style* visitedStyle = nullptr;
+
+    void visit(Reference* ref) override {
+        visitedRefs.push_back(ref);
+    }
+
+    void visit(Style* style) override {
+        visitedStyle = style;
+        ValueVisitor::visit(style);
+    }
+};
+
+TEST(ValueVisitorTest, VisitsReference) {
+    Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"});
+    SingleReferenceVisitor visitor;
+    ref.accept(&visitor);
+
+    EXPECT_EQ(visitor.visited, &ref);
+}
+
+TEST(ValueVisitorTest, VisitsReferencesInStyle) {
+    std::unique_ptr<Style> style = test::StyleBuilder()
+            .setParent(u"@android:style/foo")
+            .addItem(u"@android:attr/one", test::buildReference(u"@android:id/foo"))
+            .build();
+
+    StyleVisitor visitor;
+    style->accept(&visitor);
+
+    ASSERT_EQ(style.get(), visitor.visitedStyle);
+
+    // Entry attribute references, plus the parent reference, plus one value reference.
+    ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
+}
+
+TEST(ValueVisitorTest, ValueCast) {
+    std::unique_ptr<Reference> ref = test::buildReference(u"@android:color/white");
+    EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
+
+    std::unique_ptr<Style> style = test::StyleBuilder()
+            .addItem(u"@android:attr/foo", test::buildReference(u"@android:color/black"))
+            .build();
+    EXPECT_NE(valueCast<Style>(style.get()), nullptr);
+    EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp
deleted file mode 100644
index 31115f2..0000000
--- a/tools/aapt2/XliffXmlPullParser.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "XliffXmlPullParser.h"
-
-#include <string>
-
-namespace aapt {
-
-XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
-        mParser(parser) {
-}
-
-XmlPullParser::Event XliffXmlPullParser::next() {
-    while (XmlPullParser::isGoodEvent(mParser->next())) {
-        Event event = mParser->getEvent();
-        if (event != Event::kStartElement && event != Event::kEndElement) {
-            break;
-        }
-
-        if (mParser->getElementNamespace() !=
-                u"urn:oasis:names:tc:xliff:document:1.2") {
-            break;
-        }
-
-        const std::u16string& name = mParser->getElementName();
-        if (name != u"bpt"
-                && name != u"ept"
-                && name != u"it"
-                && name != u"ph"
-                && name != u"g"
-                && name != u"bx"
-                && name != u"ex"
-                && name != u"x") {
-            break;
-        }
-
-        // We hit a tag that was ignored, so get the next event.
-    }
-    return mParser->getEvent();
-}
-
-XmlPullParser::Event XliffXmlPullParser::getEvent() const {
-    return mParser->getEvent();
-}
-
-const std::string& XliffXmlPullParser::getLastError() const {
-    return mParser->getLastError();
-}
-
-const std::u16string& XliffXmlPullParser::getComment() const {
-    return mParser->getComment();
-}
-
-size_t XliffXmlPullParser::getLineNumber() const {
-    return mParser->getLineNumber();
-}
-
-size_t XliffXmlPullParser::getDepth() const {
-    return mParser->getDepth();
-}
-
-const std::u16string& XliffXmlPullParser::getText() const {
-    return mParser->getText();
-}
-
-const std::u16string& XliffXmlPullParser::getNamespacePrefix() const {
-    return mParser->getNamespacePrefix();
-}
-
-const std::u16string& XliffXmlPullParser::getNamespaceUri() const {
-    return mParser->getNamespaceUri();
-}
-
-bool XliffXmlPullParser::applyPackageAlias(std::u16string* package,
-                                           const std::u16string& defaultPackage) const {
-    return mParser->applyPackageAlias(package, defaultPackage);
-}
-
-const std::u16string& XliffXmlPullParser::getElementNamespace() const {
-    return mParser->getElementNamespace();
-}
-
-const std::u16string& XliffXmlPullParser::getElementName() const {
-    return mParser->getElementName();
-}
-
-size_t XliffXmlPullParser::getAttributeCount() const {
-    return mParser->getAttributeCount();
-}
-
-XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const {
-    return mParser->beginAttributes();
-}
-
-XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const {
-    return mParser->endAttributes();
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/XliffXmlPullParser.h b/tools/aapt2/XliffXmlPullParser.h
deleted file mode 100644
index 7791227..0000000
--- a/tools/aapt2/XliffXmlPullParser.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_XLIFF_XML_PULL_PARSER_H
-#define AAPT_XLIFF_XML_PULL_PARSER_H
-
-#include "XmlPullParser.h"
-
-#include <memory>
-#include <string>
-
-namespace aapt {
-
-/**
- * Strips xliff elements and provides the caller with a view of the
- * underlying XML without xliff.
- */
-class XliffXmlPullParser : public XmlPullParser {
-public:
-    XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
-    XliffXmlPullParser(const XliffXmlPullParser& rhs) = delete;
-
-    Event getEvent() const override;
-    const std::string& getLastError() const override;
-    Event next() override;
-
-    const std::u16string& getComment() const override;
-    size_t getLineNumber() const override;
-    size_t getDepth() const override;
-
-    const std::u16string& getText() const override;
-
-    const std::u16string& getNamespacePrefix() const override;
-    const std::u16string& getNamespaceUri() const override;
-    bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage)
-            const override;
-
-    const std::u16string& getElementNamespace() const override;
-    const std::u16string& getElementName() const override;
-
-    const_iterator beginAttributes() const override;
-    const_iterator endAttributes() const override;
-    size_t getAttributeCount() const override;
-
-private:
-    std::shared_ptr<XmlPullParser> mParser;
-};
-
-} // namespace aapt
-
-#endif // AAPT_XLIFF_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XliffXmlPullParser_test.cpp b/tools/aapt2/XliffXmlPullParser_test.cpp
deleted file mode 100644
index f9030724..0000000
--- a/tools/aapt2/XliffXmlPullParser_test.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SourceXmlPullParser.h"
-#include "XliffXmlPullParser.h"
-
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-TEST(XliffXmlPullParserTest, IgnoreXliffTags) {
-    std::stringstream input;
-    input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
-          << "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl
-          << "<string name=\"foo\">"
-          << "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl
-          << "</resources>" << std::endl;
-    std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
-    XliffXmlPullParser parser(sourceParser);
-    EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent());
-
-    EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
-    EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
-    EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"resources");
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
-
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"string");
-
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
-    EXPECT_EQ(parser.getText(), u"Hey ");
-
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
-    EXPECT_EQ(parser.getText(), u"there");
-
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
-    EXPECT_EQ(parser.getText(), u" world");
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"string");
-    EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
-
-    EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
-    EXPECT_EQ(parser.getElementNamespace(), u"");
-    EXPECT_EQ(parser.getElementName(), u"resources");
-
-    EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
-    EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
-    EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
-
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next());
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp
index b8b2d12..d948775 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/XmlDom.cpp
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#include "Logger.h"
-#include "Util.h"
+#include "util/Util.h"
 #include "XmlDom.h"
 #include "XmlPullParser.h"
 
@@ -65,7 +64,7 @@
         stack->root = std::move(node);
     }
 
-    if (thisNode->type != NodeType::kText) {
+    if (!nodeCast<Text>(thisNode)) {
         stack->nodeStack.push(thisNode);
     }
 }
@@ -143,8 +142,7 @@
         Node* currentParent = stack->nodeStack.top();
         if (!currentParent->children.empty()) {
             Node* lastChild = currentParent->children.back().get();
-            if (lastChild->type == NodeType::kText) {
-                Text* text = static_cast<Text*>(lastChild);
+            if (Text* text = nodeCast<Text>(lastChild)) {
                 text->text += util::utf8ToUtf16(StringPiece(s, len));
                 return;
             }
@@ -166,7 +164,7 @@
     stack->pendingComment += util::utf8ToUtf16(comment);
 }
 
-std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) {
+std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) {
     Stack stack;
 
     XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
@@ -182,20 +180,23 @@
         in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
         if (in->bad() && !in->eof()) {
             stack.root = {};
-            logger->error() << strerror(errno) << std::endl;
+            diag->error(DiagMessage(source) << strerror(errno));
             break;
         }
 
         if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
             stack.root = {};
-            logger->error(XML_GetCurrentLineNumber(parser))
-                    << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl;
+            diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser)))
+                        << XML_ErrorString(XML_GetErrorCode(parser)));
             break;
         }
     }
 
     XML_ParserFree(parser);
-    return std::move(stack.root);
+    if (stack.root) {
+        return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root));
+    }
+    return {};
 }
 
 static void copyAttributes(Element* el, android::ResXMLParser* parser) {
@@ -224,7 +225,8 @@
     }
 }
 
-std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) {
+std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
+                                     const Source& source) {
     std::unique_ptr<Node> root;
     std::stack<Node*> nodeStack;
 
@@ -307,15 +309,12 @@
                 nodeStack.top()->addChild(std::move(newNode));
             }
 
-            if (thisNode->type != NodeType::kText) {
+            if (!nodeCast<Text>(thisNode)) {
                 nodeStack.push(thisNode);
             }
         }
     }
-    return root;
-}
-
-Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) {
+    return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
 }
 
 void Node::addChild(std::unique_ptr<Node> child) {
@@ -323,39 +322,6 @@
     children.push_back(std::move(child));
 }
 
-Namespace::Namespace() : BaseNode(NodeType::kNamespace) {
-}
-
-std::unique_ptr<Node> Namespace::clone() const {
-    Namespace* ns = new Namespace();
-    ns->lineNumber = lineNumber;
-    ns->columnNumber = columnNumber;
-    ns->comment = comment;
-    ns->namespacePrefix = namespacePrefix;
-    ns->namespaceUri = namespaceUri;
-    for (auto& child : children) {
-        ns->addChild(child->clone());
-    }
-    return std::unique_ptr<Node>(ns);
-}
-
-Element::Element() : BaseNode(NodeType::kElement) {
-}
-
-std::unique_ptr<Node> Element::clone() const {
-    Element* el = new Element();
-    el->lineNumber = lineNumber;
-    el->columnNumber = columnNumber;
-    el->comment = comment;
-    el->namespaceUri = namespaceUri;
-    el->name = name;
-    el->attributes = attributes;
-    for (auto& child : children) {
-        el->addChild(child->clone());
-    }
-    return std::unique_ptr<Node>(el);
-}
-
 Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) {
     for (auto& attr : attributes) {
         if (ns == attr.namespaceUri && name == attr.name) {
@@ -366,29 +332,29 @@
 }
 
 Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) {
-    return findChildWithAttribute(ns, name, nullptr);
+    return findChildWithAttribute(ns, name, {}, {}, {});
 }
 
 Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
-                                         const Attribute* reqAttr) {
+                                         const StringPiece16& attrNs, const StringPiece16& attrName,
+                                         const StringPiece16& attrValue) {
     for (auto& childNode : children) {
         Node* child = childNode.get();
-        while (child->type == NodeType::kNamespace) {
+        while (nodeCast<Namespace>(child)) {
             if (child->children.empty()) {
                 break;
             }
             child = child->children[0].get();
         }
 
-        if (child->type == NodeType::kElement) {
-            Element* el = static_cast<Element*>(child);
+        if (Element* el = nodeCast<Element>(child)) {
             if (ns == el->namespaceUri && name == el->name) {
-                if (!reqAttr) {
+                if (attrNs.empty() && attrName.empty()) {
                     return el;
                 }
 
-                Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name);
-                if (attrName && attrName->value == reqAttr->value) {
+                Attribute* attr = el->findAttribute(attrNs, attrName);
+                if (attr && attrValue == attr->value) {
                     return el;
                 }
             }
@@ -401,31 +367,19 @@
     std::vector<Element*> elements;
     for (auto& childNode : children) {
         Node* child = childNode.get();
-        while (child->type == NodeType::kNamespace) {
+        while (nodeCast<Namespace>(child)) {
             if (child->children.empty()) {
                 break;
             }
             child = child->children[0].get();
         }
 
-        if (child->type == NodeType::kElement) {
-            elements.push_back(static_cast<Element*>(child));
+        if (Element* el = nodeCast<Element>(child)) {
+            elements.push_back(el);
         }
     }
     return elements;
 }
 
-Text::Text() : BaseNode(NodeType::kText) {
-}
-
-std::unique_ptr<Node> Text::clone() const {
-    Text* el = new Text();
-    el->lineNumber = lineNumber;
-    el->columnNumber = columnNumber;
-    el->comment = comment;
-    el->text = text;
-    return std::unique_ptr<Node>(el);
-}
-
 } // namespace xml
 } // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index 035e7c4..c095f08 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -17,8 +17,13 @@
 #ifndef AAPT_XML_DOM_H
 #define AAPT_XML_DOM_H
 
-#include "Logger.h"
-#include "StringPiece.h"
+#include "Diagnostics.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
 
 #include <istream>
 #include <expat.h>
@@ -29,7 +34,7 @@
 namespace aapt {
 namespace xml {
 
-struct Visitor;
+struct RawVisitor;
 
 /**
  * The type of node. Can be used to downcast to the concrete XML node
@@ -45,17 +50,14 @@
  * Base class for all XML nodes.
  */
 struct Node {
-    NodeType type;
-    Node* parent;
-    size_t lineNumber;
-    size_t columnNumber;
+    Node* parent = nullptr;
+    size_t lineNumber = 0;
+    size_t columnNumber = 0;
     std::u16string comment;
     std::vector<std::unique_ptr<Node>> children;
 
-    Node(NodeType type);
     void addChild(std::unique_ptr<Node> child);
-    virtual std::unique_ptr<Node> clone() const = 0;
-    virtual void accept(Visitor* visitor) = 0;
+    virtual void accept(RawVisitor* visitor) = 0;
     virtual ~Node() {}
 };
 
@@ -65,8 +67,7 @@
  */
 template <typename Derived>
 struct BaseNode : public Node {
-    BaseNode(NodeType t);
-    virtual void accept(Visitor* visitor) override;
+    virtual void accept(RawVisitor* visitor) override;
 };
 
 /**
@@ -75,9 +76,11 @@
 struct Namespace : public BaseNode<Namespace> {
     std::u16string namespacePrefix;
     std::u16string namespaceUri;
+};
 
-    Namespace();
-    virtual std::unique_ptr<Node> clone() const override;
+struct AaptAttribute {
+    ResourceId id;
+    aapt::Attribute attribute;
 };
 
 /**
@@ -87,6 +90,9 @@
     std::u16string namespaceUri;
     std::u16string name;
     std::u16string value;
+
+    Maybe<AaptAttribute> compiledAttribute;
+    std::unique_ptr<Item> compiledValue;
 };
 
 /**
@@ -97,12 +103,12 @@
     std::u16string name;
     std::vector<Attribute> attributes;
 
-    Element();
-    virtual std::unique_ptr<Node> clone() const override;
     Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name);
     xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name);
     xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name,
-                                         const xml::Attribute* reqAttr);
+                                         const StringPiece16& attrNs,
+                                         const StringPiece16& attrName,
+                                         const StringPiece16& attrValue);
     std::vector<xml::Element*> getChildElements();
 };
 
@@ -111,41 +117,133 @@
  */
 struct Text : public BaseNode<Text> {
     std::u16string text;
-
-    Text();
-    virtual std::unique_ptr<Node> clone() const override;
 };
 
 /**
  * Inflates an XML DOM from a text stream, logging errors to the logger.
  * Returns the root node on success, or nullptr on failure.
  */
-std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger);
+std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source);
 
 /**
  * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
  * Returns the root node on success, or nullptr on failure.
  */
-std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger);
+std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
+                                     const Source& source);
 
 /**
- * A visitor interface for the different XML Node subtypes.
+ * A visitor interface for the different XML Node subtypes. This will not traverse into
+ * children. Use Visitor for that.
  */
-struct Visitor {
-    virtual void visit(Namespace* node) = 0;
-    virtual void visit(Element* node) = 0;
-    virtual void visit(Text* text) = 0;
+struct RawVisitor {
+    virtual ~RawVisitor() = default;
+
+    virtual void visit(Namespace* node) {}
+    virtual void visit(Element* node) {}
+    virtual void visit(Text* text) {}
+};
+
+/**
+ * Visitor whose default implementation visits the children nodes of any node.
+ */
+struct Visitor : public RawVisitor {
+    using RawVisitor::visit;
+
+    void visit(Namespace* node) override {
+        visitChildren(node);
+    }
+
+    void visit(Element* node) override {
+        visitChildren(node);
+    }
+
+    void visit(Text* text) override {
+        visitChildren(text);
+    }
+
+    void visitChildren(Node* node) {
+        for (auto& child : node->children) {
+            child->accept(this);
+        }
+    }
+};
+
+/**
+ * An XML DOM visitor that will record the package name for a namespace prefix.
+ */
+class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
+private:
+    struct PackageDecl {
+        std::u16string prefix;
+        std::u16string package;
+    };
+
+    std::vector<PackageDecl> mPackageDecls;
+
+public:
+    using Visitor::visit;
+
+    void visit(Namespace* ns) override {
+        bool added = false;
+        {
+            Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri);
+            if (package) {
+                mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() });
+                added = true;
+            }
+        }
+
+        Visitor::visit(ns);
+
+        if (added) {
+            mPackageDecls.pop_back();
+        }
+    }
+
+    Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                         const StringPiece16& localPackage) const override {
+        if (name.package.empty()) {
+            return ResourceName{ localPackage.toString(), name.type, name.entry };
+        }
+
+        const auto rend = mPackageDecls.rend();
+        for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
+            if (name.package == iter->prefix) {
+                if (iter->package.empty()) {
+                    return ResourceName{ localPackage.toString(), name.type, name.entry };
+                } else {
+                    return ResourceName{ iter->package, name.type, name.entry };
+                }
+            }
+        }
+        return {};
+    }
 };
 
 // Implementations
 
 template <typename Derived>
-BaseNode<Derived>::BaseNode(NodeType type) : Node(type) {
+void BaseNode<Derived>::accept(RawVisitor* visitor) {
+    visitor->visit(static_cast<Derived*>(this));
 }
 
-template <typename Derived>
-void BaseNode<Derived>::accept(Visitor* visitor) {
-    visitor->visit(static_cast<Derived*>(this));
+template <typename T>
+struct NodeCastImpl : public RawVisitor {
+    using RawVisitor::visit;
+
+    T* value = nullptr;
+
+    void visit(T* v) override {
+        value = v;
+    }
+};
+
+template <typename T>
+T* nodeCast(Node* node) {
+    NodeCastImpl<T> visitor;
+    node->accept(&visitor);
+    return visitor.value;
 }
 
 } // namespace xml
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/XmlDom_test.cpp
index 0217144..a1b9ed0 100644
--- a/tools/aapt2/XmlDom_test.cpp
+++ b/tools/aapt2/XmlDom_test.cpp
@@ -36,12 +36,13 @@
         </Layout>
     )EOF";
 
-    SourceLogger logger(Source{ "/test/path" });
-    std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
-    ASSERT_NE(root, nullptr);
+    const Source source = { "test.xml" };
+    StdErrDiagnostics diag;
+    std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source);
+    ASSERT_NE(doc, nullptr);
 
-    EXPECT_EQ(root->type, xml::NodeType::kNamespace);
-    xml::Namespace* ns = static_cast<xml::Namespace*>(root.get());
+    xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
+    ASSERT_NE(ns, nullptr);
     EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android");
     EXPECT_EQ(ns->namespacePrefix, u"android");
 }
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
deleted file mode 100644
index 56b5613d..0000000
--- a/tools/aapt2/XmlFlattener.cpp
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BigBuffer.h"
-#include "Logger.h"
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Resource.h"
-#include "ResourceParser.h"
-#include "ResourceValues.h"
-#include "SdkConstants.h"
-#include "Source.h"
-#include "StringPool.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <limits>
-#include <map>
-#include <string>
-#include <vector>
-
-namespace aapt {
-namespace xml {
-
-constexpr uint32_t kLowPriority = 0xffffffffu;
-
-// A vector that maps String refs to their final destination in the out buffer.
-using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
-
-struct XmlFlattener : public Visitor {
-    XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
-                 const std::u16string& defaultPackage) :
-            mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
-            mDefaultPackage(defaultPackage) {
-    }
-
-    // No copying.
-    XmlFlattener(const XmlFlattener&) = delete;
-    XmlFlattener& operator=(const XmlFlattener&) = delete;
-
-    void writeNamespace(Namespace* node, uint16_t type) {
-        const size_t startIndex = mOut->size();
-        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_namespaceExt* flatNs =
-                mOut->nextBlock<android::ResXMLTree_namespaceExt>();
-        mOut->align4();
-
-        flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
-        flatNode->lineNumber = node->lineNumber;
-        flatNode->comment.index = -1;
-        addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
-        addString(node->namespaceUri, kLowPriority, &flatNs->uri);
-    }
-
-    virtual void visit(Namespace* node) override {
-        // Extract the package/prefix from this namespace node.
-        Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
-        if (package) {
-            mPackageAliases.emplace_back(
-                    node->namespacePrefix,
-                    package.value().empty() ? mDefaultPackage : package.value());
-        }
-
-        writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-        writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
-
-        if (package) {
-            mPackageAliases.pop_back();
-        }
-    }
-
-    virtual void visit(Text* node) override {
-        if (util::trimWhitespace(node->text).empty()) {
-            return;
-        }
-
-        const size_t startIndex = mOut->size();
-        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
-        mOut->align4();
-
-        const uint16_t type = android::RES_XML_CDATA_TYPE;
-        flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
-        flatNode->lineNumber = node->lineNumber;
-        flatNode->comment.index = -1;
-        addString(node->text, kLowPriority, &flatText->data);
-    }
-
-    virtual void visit(Element* node) override {
-        const size_t startIndex = mOut->size();
-        android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
-
-        const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
-        flatNode->header = { type, sizeof(*flatNode), 0 };
-        flatNode->lineNumber = node->lineNumber;
-        flatNode->comment.index = -1;
-
-        addString(node->namespaceUri, kLowPriority, &flatElem->ns);
-        addString(node->name, kLowPriority, &flatElem->name);
-        flatElem->attributeStart = sizeof(*flatElem);
-        flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
-        flatElem->attributeCount = node->attributes.size();
-
-        if (!writeAttributes(mOut, node, flatElem)) {
-            mError = true;
-        }
-
-        mOut->align4();
-        flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
-
-        for (const auto& child : node->children) {
-            child->accept(this);
-        }
-
-        const size_t startEndIndex = mOut->size();
-        android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
-        android::ResXMLTree_endElementExt* flatEndElem =
-                mOut->nextBlock<android::ResXMLTree_endElementExt>();
-        mOut->align4();
-
-        const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
-        flatEndNode->header = { endType, sizeof(*flatEndNode),
-                (uint32_t)(mOut->size() - startEndIndex) };
-        flatEndNode->lineNumber = node->lineNumber;
-        flatEndNode->comment.index = -1;
-
-        addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
-        addString(node->name, kLowPriority, &flatEndElem->name);
-    }
-
-    bool success() const {
-        return !mError;
-    }
-
-protected:
-    void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
-        if (!str.empty()) {
-            mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
-        } else {
-            // The device doesn't think a string of size 0 is the same as null.
-            dest->index = -1;
-        }
-    }
-
-    void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
-        mStringRefs->emplace_back(ref, dest);
-    }
-
-    Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
-        const auto endIter = mPackageAliases.rend();
-        for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
-            if (iter->first == prefix) {
-                return iter->second;
-            }
-        }
-        return {};
-    }
-
-    const std::u16string& getDefaultPackage() const {
-        return mDefaultPackage;
-    }
-
-    /**
-     * Subclasses override this to deal with attributes. Attributes can be flattened as
-     * raw values or as resources.
-     */
-    virtual bool writeAttributes(BigBuffer* out, Element* node,
-                                 android::ResXMLTree_attrExt* flatElem) = 0;
-
-private:
-    BigBuffer* mOut;
-    StringPool* mPool;
-    FlatStringRefList* mStringRefs;
-    std::u16string mDefaultPackage;
-    bool mError = false;
-    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
-};
-
-/**
- * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
- */
-struct CompileXmlFlattener : public XmlFlattener {
-    CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
-                        const std::u16string& defaultPackage) :
-            XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
-    }
-
-    virtual bool writeAttributes(BigBuffer* out, Element* node,
-                                 android::ResXMLTree_attrExt* flatElem) override {
-        flatElem->attributeCount = node->attributes.size();
-        if (node->attributes.empty()) {
-            return true;
-        }
-
-        android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
-                node->attributes.size());
-        for (const Attribute& attr : node->attributes) {
-            addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
-            addString(attr.name, kLowPriority, &flatAttrs->name);
-            addString(attr.value, kLowPriority, &flatAttrs->rawValue);
-            flatAttrs++;
-        }
-        return true;
-    }
-};
-
-struct AttributeToFlatten {
-    uint32_t resourceId = 0;
-    const Attribute* xmlAttr = nullptr;
-    const ::aapt::Attribute* resourceAttr = nullptr;
-};
-
-static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
-    return a.resourceId < id;
-}
-
-/**
- * Flattens XML, encoding the attributes as resources.
- */
-struct LinkedXmlFlattener : public XmlFlattener {
-    LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
-                       std::map<std::u16string, StringPool>* packagePools,
-                       FlatStringRefList* stringRefs,
-                       const std::u16string& defaultPackage,
-                       const std::shared_ptr<IResolver>& resolver,
-                       SourceLogger* logger,
-                       const FlattenOptions& options) :
-            XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
-            mLogger(logger), mPackagePools(packagePools), mOptions(options) {
-    }
-
-    virtual bool writeAttributes(BigBuffer* out, Element* node,
-                                 android::ResXMLTree_attrExt* flatElem) override {
-        bool error = false;
-        std::vector<AttributeToFlatten> sortedAttributes;
-        uint32_t nextAttributeId = 0x80000000u;
-
-        // Sort and filter attributes by their resource ID.
-        for (const Attribute& attr : node->attributes) {
-            AttributeToFlatten attrToFlatten;
-            attrToFlatten.xmlAttr = &attr;
-
-            Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
-            if (package) {
-                // Find the Attribute object via our Resolver.
-                ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
-                if (attrName.package.empty()) {
-                    attrName.package = getDefaultPackage();
-                }
-
-                Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
-                if (!result || !result.value().id.isValid() || !result.value().attr) {
-                    error = true;
-                    mLogger->error(node->lineNumber)
-                            << "unresolved attribute '" << attrName << "'."
-                            << std::endl;
-                } else {
-                    attrToFlatten.resourceId = result.value().id.id;
-                    attrToFlatten.resourceAttr = result.value().attr;
-
-                    size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
-                    if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
-                        // We need to filter this attribute out.
-                        mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
-                        continue;
-                    }
-                }
-            }
-
-            if (attrToFlatten.resourceId == 0) {
-                // Attributes that have no resource ID (because they don't belong to a
-                // package) should appear after those that do have resource IDs. Assign
-                // them some integer value that will appear after.
-                attrToFlatten.resourceId = nextAttributeId++;
-            }
-
-            // Insert the attribute into the sorted vector.
-            auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
-                                         attrToFlatten.resourceId, lessAttributeId);
-            sortedAttributes.insert(iter, std::move(attrToFlatten));
-        }
-
-        flatElem->attributeCount = sortedAttributes.size();
-        if (sortedAttributes.empty()) {
-            return true;
-        }
-
-        android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
-                sortedAttributes.size());
-
-        // Now that we have sorted the attributes into their final encoded order, it's time
-        // to actually write them out.
-        uint16_t attributeIndex = 1;
-        for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
-            Maybe<std::u16string> package = util::extractPackageFromNamespace(
-                    attrToFlatten.xmlAttr->namespaceUri);
-
-            // Assign the indices for specific attributes.
-            if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
-                flatElem->idIndex = attributeIndex;
-            } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
-                if (attrToFlatten.xmlAttr->name == u"class") {
-                    flatElem->classIndex = attributeIndex;
-                } else if (attrToFlatten.xmlAttr->name == u"style") {
-                    flatElem->styleIndex = attributeIndex;
-                }
-            }
-            attributeIndex++;
-
-            // Add the namespaceUri and name to the list of StringRefs to encode.
-            addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
-            flatAttr->rawValue.index = -1;
-
-            if (!attrToFlatten.resourceAttr) {
-                addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
-            } else {
-                // We've already extracted the package successfully before.
-                assert(package);
-
-                // Attribute names are stored without packages, but we use
-                // their StringPool index to lookup their resource IDs.
-                // This will cause collisions, so we can't dedupe
-                // attribute names from different packages. We use separate
-                // pools that we later combine.
-                //
-                // Lookup the StringPool for this package and make the reference there.
-                StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
-                        attrToFlatten.xmlAttr->name,
-                        StringPool::Context{ attrToFlatten.resourceId });
-
-                // Add it to the list of strings to flatten.
-                addString(nameRef, &flatAttr->name);
-
-                if (mOptions.keepRawValues) {
-                    // Keep raw values (this is for static libraries).
-                    // TODO(with a smarter inflater for binary XML, we can do without this).
-                    addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
-                }
-            }
-
-            error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
-                                  flatAttr);
-            flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
-            flatAttr++;
-        }
-        return !error;
-    }
-
-    Maybe<size_t> getSmallestFilteredSdk() const {
-        if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
-            return {};
-        }
-        return mSmallestFilteredSdk;
-    }
-
-private:
-    bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
-                     android::ResXMLTree_attribute* flatAttr) {
-        std::unique_ptr<Item> item;
-        if (!attr) {
-            bool create = false;
-            item = ResourceParser::tryParseReference(value, &create);
-            if (!item) {
-                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
-                addString(value, kLowPriority, &flatAttr->rawValue);
-                addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
-                        &flatAttr->typedValue.data));
-                return true;
-            }
-        } else {
-            item = ResourceParser::parseItemForAttribute(value, *attr);
-            if (!item) {
-                if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
-                    mLogger->error(el->lineNumber)
-                            << "'"
-                            << value
-                            << "' is not compatible with attribute '"
-                            << *attr
-                            << "'."
-                            << std::endl;
-                    return false;
-                }
-
-                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
-                addString(value, kLowPriority, &flatAttr->rawValue);
-                addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
-                        &flatAttr->typedValue.data));
-                return true;
-            }
-        }
-
-        assert(item);
-
-        bool error = false;
-
-        // If this is a reference, resolve the name into an ID.
-        visitFunc<Reference>(*item, [&](Reference& reference) {
-            // First see if we can convert the package name from a prefix to a real
-            // package name.
-            ResourceName realName = reference.name;
-            if (!realName.package.empty()) {
-                Maybe<std::u16string> package = getPackageAlias(realName.package);
-                if (package) {
-                    realName.package = package.value();
-                }
-            } else {
-                realName.package = getDefaultPackage();
-            }
-
-            Maybe<ResourceId> result = mResolver->findId(realName);
-            if (!result || !result.value().isValid()) {
-                std::ostream& out = mLogger->error(el->lineNumber)
-                        << "unresolved reference '"
-                        << reference.name
-                        << "'";
-                if (realName != reference.name) {
-                    out << " (aka '" << realName << "')";
-                }
-                out << "'." << std::endl;
-                error = true;
-            } else {
-                reference.id = result.value();
-            }
-        });
-
-        if (error) {
-            return false;
-        }
-
-        item->flatten(flatAttr->typedValue);
-        return true;
-    }
-
-    std::shared_ptr<IResolver> mResolver;
-    SourceLogger* mLogger;
-    std::map<std::u16string, StringPool>* mPackagePools;
-    FlattenOptions mOptions;
-    size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
-};
-
-/**
- * The binary XML file expects the StringPool to appear first, but we haven't collected the
- * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
- * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
- * then move the data from the temporary BigBuffer into the given one. This incurs no
- * copies as the given BigBuffer simply takes ownership of the data.
- */
-static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
-                       BigBuffer&& xmlTreeBuffer) {
-    // Sort the string pool so that attribute resource IDs show up first.
-    pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
-        return a.context.priority < b.context.priority;
-    });
-
-    // Now we flatten the string pool references into the correct places.
-    for (const auto& refEntry : *stringRefs) {
-        refEntry.second->index = refEntry.first.getIndex();
-    }
-
-    // Write the XML header.
-    const size_t beforeXmlTreeIndex = outBuffer->size();
-    android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
-    header->header.type = android::RES_XML_TYPE;
-    header->header.headerSize = sizeof(*header);
-
-    // Flatten the StringPool.
-    StringPool::flattenUtf16(outBuffer, *pool);
-
-    // Write the array of resource IDs, indexed by StringPool order.
-    const size_t beforeResIdMapIndex = outBuffer->size();
-    android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
-    resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
-    resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
-    for (const auto& str : *pool) {
-        ResourceId id { str->context.priority };
-        if (id.id == kLowPriority || !id.isValid()) {
-            // When we see the first non-resource ID,
-            // we're done.
-            break;
-        }
-
-        *outBuffer->nextBlock<uint32_t>() = id.id;
-    }
-    resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
-
-    // Move the temporary BigBuffer into outBuffer.
-    outBuffer->appendBuffer(std::move(xmlTreeBuffer));
-    header->header.size = outBuffer->size() - beforeXmlTreeIndex;
-}
-
-bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
-    StringPool pool;
-
-    // This will hold the StringRefs and the location in which to write the index.
-    // Once we sort the StringPool, we can assign the updated indices
-    // to the correct data locations.
-    FlatStringRefList stringRefs;
-
-    // Since we don't know the size of the final StringPool, we write to this
-    // temporary BigBuffer, which we will append to outBuffer later.
-    BigBuffer out(1024);
-
-    CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
-    root->accept(&flattener);
-
-    if (!flattener.success()) {
-        return false;
-    }
-
-    flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
-    return true;
-};
-
-Maybe<size_t> flattenAndLink(const Source& source, Node* root,
-                             const std::u16string& defaultPackage,
-                             const std::shared_ptr<IResolver>& resolver,
-                             const FlattenOptions& options, BigBuffer* outBuffer) {
-    SourceLogger logger(source);
-    StringPool pool;
-
-    // Attribute names are stored without packages, but we use
-    // their StringPool index to lookup their resource IDs.
-    // This will cause collisions, so we can't dedupe
-    // attribute names from different packages. We use separate
-    // pools that we later combine.
-    std::map<std::u16string, StringPool> packagePools;
-
-    FlatStringRefList stringRefs;
-
-    // Since we don't know the size of the final StringPool, we write to this
-    // temporary BigBuffer, which we will append to outBuffer later.
-    BigBuffer out(1024);
-
-    LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
-                                 &logger, options);
-    root->accept(&flattener);
-
-    if (!flattener.success()) {
-        return {};
-    }
-
-    // Merge the package pools into the main pool.
-    for (auto& packagePoolEntry : packagePools) {
-        pool.merge(std::move(packagePoolEntry.second));
-    }
-
-    flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
-
-    if (flattener.getSmallestFilteredSdk()) {
-        return flattener.getSmallestFilteredSdk();
-    }
-    return 0;
-}
-
-} // namespace xml
-} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
deleted file mode 100644
index 4ece0a3..0000000
--- a/tools/aapt2/XmlFlattener.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_XML_FLATTENER_H
-#define AAPT_XML_FLATTENER_H
-
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "Resolver.h"
-#include "Source.h"
-#include "XmlDom.h"
-
-#include <string>
-
-namespace aapt {
-namespace xml {
-
-/**
- * Flattens an XML file into a binary representation parseable by
- * the Android resource system.
- */
-bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer);
-
-/**
- * Options for flattenAndLink.
- */
-struct FlattenOptions {
-    /**
-     * Keep attribute raw string values along with typed values.
-     */
-    bool keepRawValues = false;
-
-    /**
-     * If set, any attribute introduced in a later SDK will not be encoded.
-     */
-    Maybe<size_t> maxSdkAttribute;
-};
-
-/**
- * Like flatten(Node*,BigBuffer*), but references to resources are checked
- * and string values are transformed to typed data where possible.
- *
- * `defaultPackage` is used when a reference has no package or the namespace URI
- * "http://schemas.android.com/apk/res-auto" is used.
- *
- * `resolver` is used to resolve references to resources.
- */
-Maybe<size_t> flattenAndLink(const Source& source, Node* root,
-                             const std::u16string& defaultPackage,
-                             const std::shared_ptr<IResolver>& resolver,
-                             const FlattenOptions& options, BigBuffer* outBuffer);
-
-} // namespace xml
-} // namespace aapt
-
-#endif // AAPT_XML_FLATTENER_H
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
deleted file mode 100644
index 8915d24..0000000
--- a/tools/aapt2/XmlFlattener_test.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "MockResolver.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "Util.h"
-#include "XmlFlattener.h"
-
-#include <androidfw/AssetManager.h>
-#include <androidfw/ResourceTypes.h>
-#include <gtest/gtest.h>
-#include <sstream>
-#include <string>
-
-using namespace android;
-
-namespace aapt {
-namespace xml {
-
-constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-
-class XmlFlattenerTest : public ::testing::Test {
-public:
-    virtual void SetUp() override {
-        mResolver = std::make_shared<MockResolver>(
-                std::make_shared<ResourceTable>(),
-                std::map<ResourceName, ResourceId>({
-                        { ResourceName{ u"android", ResourceType::kAttr, u"attr" },
-                          ResourceId{ 0x01010000u } },
-                        { ResourceName{ u"android", ResourceType::kId, u"id" },
-                          ResourceId{ 0x01020000u } },
-                        { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" },
-                          ResourceId{ 0x01010001u } },
-                        { ResourceName{ u"com.lib", ResourceType::kId, u"id" },
-                          ResourceId{ 0x01020001u } }}));
-    }
-
-    ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) {
-        std::stringstream input(kXmlPreamble);
-        input << in << std::endl;
-
-        SourceLogger logger(Source{ "test.xml" });
-        std::unique_ptr<Node> root = inflate(&input, &logger);
-        if (!root) {
-            return ::testing::AssertionFailure();
-        }
-
-        BigBuffer outBuffer(1024);
-        if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"),
-                    mResolver, {}, &outBuffer)) {
-            return ::testing::AssertionFailure();
-        }
-
-        std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
-        if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) {
-            return ::testing::AssertionFailure();
-        }
-        return ::testing::AssertionSuccess();
-    }
-
-    std::shared_ptr<IResolver> mResolver;
-};
-
-TEST_F(XmlFlattenerTest, ParseSimpleView) {
-    std::string input = R"EOF(
-        <View xmlns:android="http://schemas.android.com/apk/res/android"
-              android:attr="@id/id"
-              class="str"
-              style="@id/id">
-        </View>
-    )EOF";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-    }
-
-    const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android";
-    const StringPiece16 attrName = u"attr";
-    ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(),
-                                        attrName.size());
-    ASSERT_GE(idx, 0);
-    EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u);
-    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
-
-    const StringPiece16 class16 = u"class";
-    idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size());
-    ASSERT_GE(idx, 0);
-    EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
-    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING);
-    EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx));
-
-    const StringPiece16 style16 = u"style";
-    idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size());
-    ASSERT_GE(idx, 0);
-    EXPECT_EQ(tree.getAttributeNameResID(idx), 0u);
-    EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE);
-    EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u);
-    EXPECT_EQ(tree.getAttributeValueStringID(idx), -1);
-
-    while (tree.next() != ResXMLTree::END_DOCUMENT) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-    }
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) {
-    std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      ns1:attr=\"@ns2:id/id\">\n"
-                        "</View>";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::END_DOCUMENT) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-    }
-}
-
-::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index,
-                                                       ResourceId nameId, ResourceId valueId) {
-    if (index >= tree->getAttributeCount()) {
-        return ::testing::AssertionFailure() << "index " << index << " is out of bounds ("
-                                             << tree->getAttributeCount() << ")";
-    }
-
-    if (tree->getAttributeNameResID(index) != nameId.id) {
-        return ::testing::AssertionFailure()
-                << "attribute at index " << index << " has ID "
-                << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) }
-                << ". Expected ID " << nameId;
-    }
-
-    if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) {
-        return ::testing::AssertionFailure() << "attribute at index " << index << " has value of "
-                                             << "type " << std::hex
-                                             << tree->getAttributeDataType(index) << std::dec
-                                             << ". Expected reference (" << std::hex
-                                             << Res_value::TYPE_REFERENCE << std::dec << ")";
-    }
-
-    if ((uint32_t) tree->getAttributeData(index) != valueId.id) {
-        return ::testing::AssertionFailure()
-                << "attribute at index " << index << " has value " << "with ID "
-                << ResourceId{ (uint32_t) tree->getAttributeData(index) }
-                << ". Expected ID " << valueId;
-    }
-    return ::testing::AssertionSuccess();
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) {
-    std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      app:attr=\"@app:id/id\">\n"
-                        "  <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n"
-                        "        app:attr=\"@app:id/id\"/>\n"
-                        "</View>";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u },
-                                            ResourceId{ 0x01020000u }));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
-                                            ResourceId{ 0x01020001u }));
-}
-
-TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) {
-    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n"
-                        "      android:attr=\"@id/id\"/>";
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace
-    // assignment.
-    // However, we didn't give '@id/id' a package, so it should use the default package
-    // 'android', and not be converted from 'android' to 'com.lib'.
-    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
-                                            ResourceId{ 0x01020000u }));
-}
-
-/*
- * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
- * namespace.
- */
-TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
-    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
-                        "      package=\"android\"/>";
-
-    ResXMLTree tree;
-    ASSERT_TRUE(testFlatten(input, &tree));
-
-    while (tree.next() != ResXMLTree::START_TAG) {
-        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
-        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
-    }
-
-    const StringPiece16 kPackage = u"package";
-    EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
-}
-
-} // namespace xml
-} // namespace aapt
diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/XmlPullParser.cpp
similarity index 73%
rename from tools/aapt2/SourceXmlPullParser.cpp
rename to tools/aapt2/XmlPullParser.cpp
index 8099044..1b9499d 100644
--- a/tools/aapt2/SourceXmlPullParser.cpp
+++ b/tools/aapt2/XmlPullParser.cpp
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
+#include "util/Maybe.h"
+#include "util/Util.h"
+#include "XmlPullParser.h"
+
 #include <iostream>
 #include <string>
 
-#include "Maybe.h"
-#include "SourceXmlPullParser.h"
-#include "Util.h"
-
 namespace aapt {
 
 constexpr char kXmlNamespaceSep = 1;
 
-SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
+XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
     mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
     XML_SetUserData(mParser, this);
     XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
@@ -35,11 +35,11 @@
     mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
 }
 
-SourceXmlPullParser::~SourceXmlPullParser() {
+XmlPullParser::~XmlPullParser() {
     XML_ParserFree(mParser);
 }
 
-SourceXmlPullParser::Event SourceXmlPullParser::next() {
+XmlPullParser::Event XmlPullParser::next() {
     const Event currentEvent = getEvent();
     if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
         return currentEvent;
@@ -88,34 +88,34 @@
     return event;
 }
 
-SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const {
+XmlPullParser::Event XmlPullParser::getEvent() const {
     return mEventQueue.front().event;
 }
 
-const std::string& SourceXmlPullParser::getLastError() const {
+const std::string& XmlPullParser::getLastError() const {
     return mLastError;
 }
 
-const std::u16string& SourceXmlPullParser::getComment() const {
+const std::u16string& XmlPullParser::getComment() const {
     return mEventQueue.front().comment;
 }
 
-size_t SourceXmlPullParser::getLineNumber() const {
+size_t XmlPullParser::getLineNumber() const {
     return mEventQueue.front().lineNumber;
 }
 
-size_t SourceXmlPullParser::getDepth() const {
+size_t XmlPullParser::getDepth() const {
     return mEventQueue.front().depth;
 }
 
-const std::u16string& SourceXmlPullParser::getText() const {
+const std::u16string& XmlPullParser::getText() const {
     if (getEvent() != Event::kText) {
         return mEmpty;
     }
     return mEventQueue.front().data1;
 }
 
-const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
+const std::u16string& XmlPullParser::getNamespacePrefix() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
         return mEmpty;
@@ -123,7 +123,7 @@
     return mEventQueue.front().data1;
 }
 
-const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
+const std::u16string& XmlPullParser::getNamespaceUri() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
         return mEmpty;
@@ -131,23 +131,26 @@
     return mEventQueue.front().data2;
 }
 
-bool SourceXmlPullParser::applyPackageAlias(std::u16string* package,
-                                            const std::u16string& defaultPackage) const {
+Maybe<ResourceName> XmlPullParser::transformPackage(
+        const ResourceName& name, const StringPiece16& localPackage) const {
+    if (name.package.empty()) {
+        return ResourceName{ localPackage.toString(), name.type, name.entry };
+    }
+
     const auto endIter = mPackageAliases.rend();
     for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
-        if (iter->first == *package) {
+        if (name.package == iter->first) {
             if (iter->second.empty()) {
-                *package = defaultPackage;
+                return ResourceName{ localPackage.toString(), name.type, name.entry };
             } else {
-                *package = iter->second;
+                return ResourceName{ iter->second, name.type, name.entry };
             }
-            return true;
         }
     }
-    return false;
+    return {};
 }
 
-const std::u16string& SourceXmlPullParser::getElementNamespace() const {
+const std::u16string& XmlPullParser::getElementNamespace() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
         return mEmpty;
@@ -155,7 +158,7 @@
     return mEventQueue.front().data1;
 }
 
-const std::u16string& SourceXmlPullParser::getElementName() const {
+const std::u16string& XmlPullParser::getElementName() const {
     const Event currentEvent = getEvent();
     if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
         return mEmpty;
@@ -163,15 +166,15 @@
     return mEventQueue.front().data2;
 }
 
-XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const {
+XmlPullParser::const_iterator XmlPullParser::beginAttributes() const {
     return mEventQueue.front().attributes.begin();
 }
 
-XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const {
+XmlPullParser::const_iterator XmlPullParser::endAttributes() const {
     return mEventQueue.front().attributes.end();
 }
 
-size_t SourceXmlPullParser::getAttributeCount() const {
+size_t XmlPullParser::getAttributeCount() const {
     if (getEvent() != Event::kStartElement) {
         return 0;
     }
@@ -196,9 +199,9 @@
     }
 }
 
-void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
+void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
         const char* uri) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
     std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string();
     parser->mNamespaceUris.push(namespaceUri);
     parser->mEventQueue.push(EventData{
@@ -210,9 +213,9 @@
     });
 }
 
-void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name,
+void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name,
         const char** attrs) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     EventData data = {
             Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
@@ -233,8 +236,8 @@
     parser->mEventQueue.push(std::move(data));
 }
 
-void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     parser->mEventQueue.push(EventData{
             Event::kText,
@@ -244,8 +247,8 @@
     });
 }
 
-void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     EventData data = {
             Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
@@ -256,8 +259,8 @@
     parser->mEventQueue.push(std::move(data));
 }
 
-void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     parser->mEventQueue.push(EventData{
             Event::kEndNamespace,
@@ -269,8 +272,8 @@
     parser->mNamespaceUris.pop();
 }
 
-void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) {
-    SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
+void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) {
+    XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
 
     parser->mEventQueue.push(EventData{
             Event::kComment,
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h
index accfd30a..f7d7a03 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/XmlPullParser.h
@@ -17,16 +17,24 @@
 #ifndef AAPT_XML_PULL_PARSER_H
 #define AAPT_XML_PULL_PARSER_H
 
+#include "util/Maybe.h"
+#include "Resource.h"
+#include "util/StringPiece.h"
+
+#include "process/IResourceTableConsumer.h"
+
 #include <algorithm>
+#include <expat.h>
+#include <istream>
 #include <ostream>
+#include <queue>
+#include <stack>
 #include <string>
 #include <vector>
 
-#include "StringPiece.h"
-
 namespace aapt {
 
-class XmlPullParser {
+class XmlPullParser : public IPackageDeclStack {
 public:
     enum class Event {
         kBadDocument,
@@ -41,43 +49,51 @@
         kComment,
     };
 
-    static void skipCurrentElement(XmlPullParser* parser);
+    /**
+     * Skips to the next direct descendant node of the given startDepth,
+     * skipping namespace nodes.
+     *
+     * When nextChildNode returns true, you can expect Comments, Text, and StartElement events.
+     */
+    static bool nextChildNode(XmlPullParser* parser, size_t startDepth);
+    static bool skipCurrentElement(XmlPullParser* parser);
     static bool isGoodEvent(Event event);
 
-    virtual ~XmlPullParser() {}
+    XmlPullParser(std::istream& in);
+    virtual ~XmlPullParser();
 
     /**
      * Returns the current event that is being processed.
      */
-    virtual Event getEvent() const = 0;
+    Event getEvent() const;
 
-    virtual const std::string& getLastError() const = 0;
+    const std::string& getLastError() const;
 
     /**
      * Note, unlike XmlPullParser, the first call to next() will return
      * StartElement of the first element.
      */
-    virtual Event next() = 0;
+    Event next();
 
     //
     // These are available for all nodes.
     //
 
-    virtual const std::u16string& getComment() const = 0;
-    virtual size_t getLineNumber() const = 0;
-    virtual size_t getDepth() const = 0;
+    const std::u16string& getComment() const;
+    size_t getLineNumber() const;
+    size_t getDepth() const;
 
     /**
      * Returns the character data for a Text event.
      */
-    virtual const std::u16string& getText() const = 0;
+    const std::u16string& getText() const;
 
     //
     // Namespace prefix and URI are available for StartNamespace and EndNamespace.
     //
 
-    virtual const std::u16string& getNamespacePrefix() const = 0;
-    virtual const std::u16string& getNamespaceUri() const = 0;
+    const std::u16string& getNamespacePrefix() const;
+    const std::u16string& getNamespaceUri() const;
 
     /*
      * Uses the current stack of namespaces to resolve the package. Eg:
@@ -90,15 +106,17 @@
      * If xmlns:app="http://schemas.android.com/apk/res-auto", then
      * 'package' will be set to 'defaultPackage'.
      */
-    virtual bool applyPackageAlias(std::u16string* package,
-                                   const std::u16string& defaultPackage) const = 0;
+    //
 
     //
     // These are available for StartElement and EndElement.
     //
 
-    virtual const std::u16string& getElementNamespace() const = 0;
-    virtual const std::u16string& getElementName() const = 0;
+    const std::u16string& getElementNamespace() const;
+    const std::u16string& getElementName() const;
+
+    Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                         const StringPiece16& localPackage) const override;
 
     //
     // Remaining methods are for retrieving information about attributes
@@ -121,10 +139,38 @@
 
     using const_iterator = std::vector<Attribute>::const_iterator;
 
-    virtual const_iterator beginAttributes() const = 0;
-    virtual const_iterator endAttributes() const = 0;
-    virtual size_t getAttributeCount() const = 0;
+    const_iterator beginAttributes() const;
+    const_iterator endAttributes() const;
+    size_t getAttributeCount() const;
     const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const;
+
+private:
+    static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
+    static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
+    static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
+    static void XMLCALL endElementHandler(void* userData, const char* name);
+    static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
+    static void XMLCALL commentDataHandler(void* userData, const char* comment);
+
+    struct EventData {
+        Event event;
+        size_t lineNumber;
+        size_t depth;
+        std::u16string data1;
+        std::u16string data2;
+        std::u16string comment;
+        std::vector<Attribute> attributes;
+    };
+
+    std::istream& mIn;
+    XML_Parser mParser;
+    char mBuffer[16384];
+    std::queue<EventData> mEventQueue;
+    std::string mLastError;
+    const std::u16string mEmpty;
+    size_t mDepth;
+    std::stack<std::u16string> mNamespaceUris;
+    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
 };
 
 //
@@ -146,13 +192,35 @@
     return out;
 }
 
-inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
+inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) {
+    Event event;
+
+    // First get back to the start depth.
+    while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {}
+
+    // Now look for the first good node.
+    while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) {
+        switch (event) {
+        case Event::kText:
+        case Event::kComment:
+        case Event::kStartElement:
+            return true;
+        default:
+            break;
+        }
+        event = parser->next();
+    }
+    return false;
+}
+
+inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
     int depth = 1;
     while (depth > 0) {
         switch (parser->next()) {
             case Event::kEndDocument:
+                return true;
             case Event::kBadDocument:
-                return;
+                return false;
             case Event::kStartElement:
                 depth++;
                 break;
@@ -163,6 +231,7 @@
                 break;
         }
     }
+    return true;
 }
 
 inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/XmlPullParser_test.cpp
new file mode 100644
index 0000000..1c99a43
--- /dev/null
+++ b/tools/aapt2/XmlPullParser_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "util/StringPiece.h"
+#include "XmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+
+namespace aapt {
+
+TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
+    std::stringstream str;
+    str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+            "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
+    XmlPullParser parser(str);
+
+    const size_t depthOuter = parser.getDepth();
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter));
+
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
+
+    const size_t depthA = parser.getDepth();
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA));
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName()));
+
+    const size_t depthB = parser.getDepth();
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName()));
+
+    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
+
+    ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter));
+    EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp
deleted file mode 100644
index 891b4e1..0000000
--- a/tools/aapt2/ZipEntry.cpp
+++ /dev/null
@@ -1,745 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// Access to entries in a Zip archive.
-//
-
-#define LOG_TAG "zip"
-
-#include "ZipEntry.h"
-#include <utils/Log.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Initialize a new ZipEntry structure from a FILE* positioned at a
- * CentralDirectoryEntry.
- *
- * On exit, the file pointer will be at the start of the next CDE or
- * at the EOCD.
- */
-status_t ZipEntry::initFromCDE(FILE* fp)
-{
-    status_t result;
-    long posn;
-    bool hasDD;
-
-    //ALOGV("initFromCDE ---\n");
-
-    /* read the CDE */
-    result = mCDE.read(fp);
-    if (result != NO_ERROR) {
-        ALOGD("mCDE.read failed\n");
-        return result;
-    }
-
-    //mCDE.dump();
-
-    /* using the info in the CDE, go load up the LFH */
-    posn = ftell(fp);
-    if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
-        ALOGD("local header seek failed (%ld)\n",
-            mCDE.mLocalHeaderRelOffset);
-        return UNKNOWN_ERROR;
-    }
-
-    result = mLFH.read(fp);
-    if (result != NO_ERROR) {
-        ALOGD("mLFH.read failed\n");
-        return result;
-    }
-
-    if (fseek(fp, posn, SEEK_SET) != 0)
-        return UNKNOWN_ERROR;
-
-    //mLFH.dump();
-
-    /*
-     * We *might* need to read the Data Descriptor at this point and
-     * integrate it into the LFH.  If this bit is set, the CRC-32,
-     * compressed size, and uncompressed size will be zero.  In practice
-     * these seem to be rare.
-     */
-    hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
-    if (hasDD) {
-        // do something clever
-        //ALOGD("+++ has data descriptor\n");
-    }
-
-    /*
-     * Sanity-check the LFH.  Note that this will fail if the "kUsesDataDescr"
-     * flag is set, because the LFH is incomplete.  (Not a problem, since we
-     * prefer the CDE values.)
-     */
-    if (!hasDD && !compareHeaders()) {
-        ALOGW("warning: header mismatch\n");
-        // keep going?
-    }
-
-    /*
-     * If the mVersionToExtract is greater than 20, we may have an
-     * issue unpacking the record -- could be encrypted, compressed
-     * with something we don't support, or use Zip64 extensions.  We
-     * can defer worrying about that to when we're extracting data.
-     */
-
-    return NO_ERROR;
-}
-
-/*
- * Initialize a new entry.  Pass in the file name and an optional comment.
- *
- * Initializes the CDE and the LFH.
- */
-void ZipEntry::initNew(const char* fileName, const char* comment)
-{
-    assert(fileName != NULL && *fileName != '\0');  // name required
-
-    /* most fields are properly initialized by constructor */
-    mCDE.mVersionMadeBy = kDefaultMadeBy;
-    mCDE.mVersionToExtract = kDefaultVersion;
-    mCDE.mCompressionMethod = kCompressStored;
-    mCDE.mFileNameLength = strlen(fileName);
-    if (comment != NULL)
-        mCDE.mFileCommentLength = strlen(comment);
-    mCDE.mExternalAttrs = 0x81b60020;   // matches what WinZip does
-
-    if (mCDE.mFileNameLength > 0) {
-        mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
-        strcpy((char*) mCDE.mFileName, fileName);
-    }
-    if (mCDE.mFileCommentLength > 0) {
-        /* TODO: stop assuming null-terminated ASCII here? */
-        mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
-        strcpy((char*) mCDE.mFileComment, comment);
-    }
-
-    copyCDEtoLFH();
-}
-
-/*
- * Initialize a new entry, starting with the ZipEntry from a different
- * archive.
- *
- * Initializes the CDE and the LFH.
- */
-status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
-    const ZipEntry* pEntry, const char* storageName)
-{
-    mCDE = pEntry->mCDE;
-    if (storageName && *storageName != 0) {
-        mCDE.mFileNameLength = strlen(storageName);
-        mCDE.mFileName = new unsigned char[mCDE.mFileNameLength + 1];
-        strcpy((char*) mCDE.mFileName, storageName);
-    }
-
-    // Check whether we got all the memory needed.
-    if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
-            (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
-            (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
-        return NO_MEMORY;
-    }
-
-    /* construct the LFH from the CDE */
-    copyCDEtoLFH();
-
-    /*
-     * The LFH "extra" field is independent of the CDE "extra", so we
-     * handle it here.
-     */
-    assert(mLFH.mExtraField == NULL);
-    mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
-    if (mLFH.mExtraFieldLength > 0) {
-        mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
-        if (mLFH.mExtraField == NULL)
-            return NO_MEMORY;
-        memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
-            mLFH.mExtraFieldLength+1);
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Insert pad bytes in the LFH by tweaking the "extra" field.  This will
- * potentially confuse something that put "extra" data in here earlier,
- * but I can't find an actual problem.
- */
-status_t ZipEntry::addPadding(int padding)
-{
-    if (padding <= 0)
-        return INVALID_OPERATION;
-
-    //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
-    //    padding, mLFH.mExtraFieldLength, mCDE.mFileName);
-
-    if (mLFH.mExtraFieldLength > 0) {
-        /* extend existing field */
-        unsigned char* newExtra;
-
-        newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
-        if (newExtra == NULL)
-            return NO_MEMORY;
-        memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
-        memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
-
-        delete[] mLFH.mExtraField;
-        mLFH.mExtraField = newExtra;
-        mLFH.mExtraFieldLength += padding;
-    } else {
-        /* create new field */
-        mLFH.mExtraField = new unsigned char[padding];
-        memset(mLFH.mExtraField, 0, padding);
-        mLFH.mExtraFieldLength = padding;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Set the fields in the LFH equal to the corresponding fields in the CDE.
- *
- * This does not touch the LFH "extra" field.
- */
-void ZipEntry::copyCDEtoLFH(void)
-{
-    mLFH.mVersionToExtract  = mCDE.mVersionToExtract;
-    mLFH.mGPBitFlag         = mCDE.mGPBitFlag;
-    mLFH.mCompressionMethod = mCDE.mCompressionMethod;
-    mLFH.mLastModFileTime   = mCDE.mLastModFileTime;
-    mLFH.mLastModFileDate   = mCDE.mLastModFileDate;
-    mLFH.mCRC32             = mCDE.mCRC32;
-    mLFH.mCompressedSize    = mCDE.mCompressedSize;
-    mLFH.mUncompressedSize  = mCDE.mUncompressedSize;
-    mLFH.mFileNameLength    = mCDE.mFileNameLength;
-    // the "extra field" is independent
-
-    delete[] mLFH.mFileName;
-    if (mLFH.mFileNameLength > 0) {
-        mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
-        strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
-    } else {
-        mLFH.mFileName = NULL;
-    }
-}
-
-/*
- * Set some information about a file after we add it.
- */
-void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
-    int compressionMethod)
-{
-    mCDE.mCompressionMethod = compressionMethod;
-    mCDE.mCRC32 = crc32;
-    mCDE.mCompressedSize = compLen;
-    mCDE.mUncompressedSize = uncompLen;
-    mCDE.mCompressionMethod = compressionMethod;
-    if (compressionMethod == kCompressDeflated) {
-        mCDE.mGPBitFlag |= 0x0002;      // indicates maximum compression used
-    }
-    copyCDEtoLFH();
-}
-
-/*
- * See if the data in mCDE and mLFH match up.  This is mostly useful for
- * debugging these classes, but it can be used to identify damaged
- * archives.
- *
- * Returns "false" if they differ.
- */
-bool ZipEntry::compareHeaders(void) const
-{
-    if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
-        ALOGV("cmp: VersionToExtract\n");
-        return false;
-    }
-    if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
-        ALOGV("cmp: GPBitFlag\n");
-        return false;
-    }
-    if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
-        ALOGV("cmp: CompressionMethod\n");
-        return false;
-    }
-    if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
-        ALOGV("cmp: LastModFileTime\n");
-        return false;
-    }
-    if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
-        ALOGV("cmp: LastModFileDate\n");
-        return false;
-    }
-    if (mCDE.mCRC32 != mLFH.mCRC32) {
-        ALOGV("cmp: CRC32\n");
-        return false;
-    }
-    if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
-        ALOGV("cmp: CompressedSize\n");
-        return false;
-    }
-    if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
-        ALOGV("cmp: UncompressedSize\n");
-        return false;
-    }
-    if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
-        ALOGV("cmp: FileNameLength\n");
-        return false;
-    }
-#if 0       // this seems to be used for padding, not real data
-    if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
-        ALOGV("cmp: ExtraFieldLength\n");
-        return false;
-    }
-#endif
-    if (mCDE.mFileName != NULL) {
-        if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
-            ALOGV("cmp: FileName\n");
-            return false;
-        }
-    }
-
-    return true;
-}
-
-
-/*
- * Convert the DOS date/time stamp into a UNIX time stamp.
- */
-time_t ZipEntry::getModWhen(void) const
-{
-    struct tm parts;
-
-    parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
-    parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
-    parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
-    parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
-    parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
-    parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
-    parts.tm_wday = parts.tm_yday = 0;
-    parts.tm_isdst = -1;        // DST info "not available"
-
-    return mktime(&parts);
-}
-
-/*
- * Set the CDE/LFH timestamp from UNIX time.
- */
-void ZipEntry::setModWhen(time_t when)
-{
-#if !defined(_WIN32)
-    struct tm tmResult;
-#endif
-    time_t even;
-    unsigned short zdate, ztime;
-
-    struct tm* ptm;
-
-    /* round up to an even number of seconds */
-    even = (time_t)(((unsigned long)(when) + 1) & (~1));
-
-    /* expand */
-#if !defined(_WIN32)
-    ptm = localtime_r(&even, &tmResult);
-#else
-    ptm = localtime(&even);
-#endif
-
-    int year;
-    year = ptm->tm_year;
-    if (year < 80)
-        year = 80;
-
-    zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
-    ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
-
-    mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
-    mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
-}
-
-
-/*
- * ===========================================================================
- *      ZipEntry::LocalFileHeader
- * ===========================================================================
- */
-
-/*
- * Read a local file header.
- *
- * On entry, "fp" points to the signature at the start of the header.
- * On exit, "fp" points to the start of data.
- */
-status_t ZipEntry::LocalFileHeader::read(FILE* fp)
-{
-    status_t result = NO_ERROR;
-    unsigned char buf[kLFHLen];
-
-    assert(mFileName == NULL);
-    assert(mExtraField == NULL);
-
-    if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
-        ALOGD("whoops: didn't find expected signature\n");
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
-    mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
-    mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
-    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
-    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
-    mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
-    mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
-    mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
-    mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
-    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
-
-    // TODO: validate sizes
-
-    /* grab filename */
-    if (mFileNameLength != 0) {
-        mFileName = new unsigned char[mFileNameLength+1];
-        if (mFileName == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mFileName[mFileNameLength] = '\0';
-    }
-
-    /* grab extra field */
-    if (mExtraFieldLength != 0) {
-        mExtraField = new unsigned char[mExtraFieldLength+1];
-        if (mExtraField == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mExtraField[mExtraFieldLength] = '\0';
-    }
-
-bail:
-    return result;
-}
-
-/*
- * Write a local file header.
- */
-status_t ZipEntry::LocalFileHeader::write(FILE* fp)
-{
-    unsigned char buf[kLFHLen];
-
-    ZipEntry::putLongLE(&buf[0x00], kSignature);
-    ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
-    ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
-    ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
-    ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
-    ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
-    ZipEntry::putLongLE(&buf[0x0e], mCRC32);
-    ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
-    ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
-    ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
-    ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
-
-    if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
-        return UNKNOWN_ERROR;
-
-    /* write filename */
-    if (mFileNameLength != 0) {
-        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
-            return UNKNOWN_ERROR;
-    }
-
-    /* write "extra field" */
-    if (mExtraFieldLength != 0) {
-        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
-            return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-
-/*
- * Dump the contents of a LocalFileHeader object.
- */
-void ZipEntry::LocalFileHeader::dump(void) const
-{
-    ALOGD(" LocalFileHeader contents:\n");
-    ALOGD("  versToExt=%u gpBits=0x%04x compression=%u\n",
-        mVersionToExtract, mGPBitFlag, mCompressionMethod);
-    ALOGD("  modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
-        mLastModFileTime, mLastModFileDate, mCRC32);
-    ALOGD("  compressedSize=%lu uncompressedSize=%lu\n",
-        mCompressedSize, mUncompressedSize);
-    ALOGD("  filenameLen=%u extraLen=%u\n",
-        mFileNameLength, mExtraFieldLength);
-    if (mFileName != NULL)
-        ALOGD("  filename: '%s'\n", mFileName);
-}
-
-
-/*
- * ===========================================================================
- *      ZipEntry::CentralDirEntry
- * ===========================================================================
- */
-
-/*
- * Read the central dir entry that appears next in the file.
- *
- * On entry, "fp" should be positioned on the signature bytes for the
- * entry.  On exit, "fp" will point at the signature word for the next
- * entry or for the EOCD.
- */
-status_t ZipEntry::CentralDirEntry::read(FILE* fp)
-{
-    status_t result = NO_ERROR;
-    unsigned char buf[kCDELen];
-
-    /* no re-use */
-    assert(mFileName == NULL);
-    assert(mExtraField == NULL);
-    assert(mFileComment == NULL);
-
-    if (fread(buf, 1, kCDELen, fp) != kCDELen) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
-        ALOGD("Whoops: didn't find expected signature\n");
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
-    mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
-    mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
-    mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
-    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
-    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
-    mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
-    mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
-    mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
-    mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
-    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
-    mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
-    mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
-    mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
-    mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
-    mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
-
-    // TODO: validate sizes and offsets
-
-    /* grab filename */
-    if (mFileNameLength != 0) {
-        mFileName = new unsigned char[mFileNameLength+1];
-        if (mFileName == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mFileName[mFileNameLength] = '\0';
-    }
-
-    /* read "extra field" */
-    if (mExtraFieldLength != 0) {
-        mExtraField = new unsigned char[mExtraFieldLength+1];
-        if (mExtraField == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mExtraField[mExtraFieldLength] = '\0';
-    }
-
-
-    /* grab comment, if any */
-    if (mFileCommentLength != 0) {
-        mFileComment = new unsigned char[mFileCommentLength+1];
-        if (mFileComment == NULL) {
-            result = NO_MEMORY;
-            goto bail;
-        }
-        if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
-        {
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        mFileComment[mFileCommentLength] = '\0';
-    }
-
-bail:
-    return result;
-}
-
-/*
- * Write a central dir entry.
- */
-status_t ZipEntry::CentralDirEntry::write(FILE* fp)
-{
-    unsigned char buf[kCDELen];
-
-    ZipEntry::putLongLE(&buf[0x00], kSignature);
-    ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
-    ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
-    ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
-    ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
-    ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
-    ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
-    ZipEntry::putLongLE(&buf[0x10], mCRC32);
-    ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
-    ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
-    ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
-    ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
-    ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
-    ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
-    ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
-    ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
-    ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
-
-    if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
-        return UNKNOWN_ERROR;
-
-    /* write filename */
-    if (mFileNameLength != 0) {
-        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
-            return UNKNOWN_ERROR;
-    }
-
-    /* write "extra field" */
-    if (mExtraFieldLength != 0) {
-        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
-            return UNKNOWN_ERROR;
-    }
-
-    /* write comment */
-    if (mFileCommentLength != 0) {
-        if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
-            return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Dump the contents of a CentralDirEntry object.
- */
-void ZipEntry::CentralDirEntry::dump(void) const
-{
-    ALOGD(" CentralDirEntry contents:\n");
-    ALOGD("  versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
-        mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
-    ALOGD("  modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
-        mLastModFileTime, mLastModFileDate, mCRC32);
-    ALOGD("  compressedSize=%lu uncompressedSize=%lu\n",
-        mCompressedSize, mUncompressedSize);
-    ALOGD("  filenameLen=%u extraLen=%u commentLen=%u\n",
-        mFileNameLength, mExtraFieldLength, mFileCommentLength);
-    ALOGD("  diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
-        mDiskNumberStart, mInternalAttrs, mExternalAttrs,
-        mLocalHeaderRelOffset);
-
-    if (mFileName != NULL)
-        ALOGD("  filename: '%s'\n", mFileName);
-    if (mFileComment != NULL)
-        ALOGD("  comment: '%s'\n", mFileComment);
-}
-
-/*
- * Copy-assignment operator for CentralDirEntry.
- */
-ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
-    if (this == &src) {
-        return *this;
-    }
-
-    // Free up old data.
-    delete[] mFileName;
-    delete[] mExtraField;
-    delete[] mFileComment;
-
-    // Copy scalars.
-    mVersionMadeBy = src.mVersionMadeBy;
-    mVersionToExtract = src.mVersionToExtract;
-    mGPBitFlag = src.mGPBitFlag;
-    mCompressionMethod = src.mCompressionMethod;
-    mLastModFileTime = src.mLastModFileTime;
-    mLastModFileDate = src.mLastModFileDate;
-    mCRC32 = src.mCRC32;
-    mCompressedSize = src.mCompressedSize;
-    mUncompressedSize = src.mUncompressedSize;
-    mFileNameLength = src.mFileNameLength;
-    mExtraFieldLength = src.mExtraFieldLength;
-    mFileCommentLength = src.mFileCommentLength;
-    mDiskNumberStart = src.mDiskNumberStart;
-    mInternalAttrs = src.mInternalAttrs;
-    mExternalAttrs = src.mExternalAttrs;
-    mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
-
-    // Copy strings, if necessary.
-    if (mFileNameLength > 0) {
-        mFileName = new unsigned char[mFileNameLength + 1];
-        if (mFileName != NULL)
-            strcpy((char*)mFileName, (char*)src.mFileName);
-    } else {
-        mFileName = NULL;
-    }
-    if (mFileCommentLength > 0) {
-        mFileComment = new unsigned char[mFileCommentLength + 1];
-        if (mFileComment != NULL)
-            strcpy((char*)mFileComment, (char*)src.mFileComment);
-    } else {
-        mFileComment = NULL;
-    }
-    if (mExtraFieldLength > 0) {
-        /* we null-terminate this, though it may not be a string */
-        mExtraField = new unsigned char[mExtraFieldLength + 1];
-        if (mExtraField != NULL)
-            memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);
-    } else {
-        mExtraField = NULL;
-    }
-
-    return *this;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h
deleted file mode 100644
index 2745a43..0000000
--- a/tools/aapt2/ZipEntry.h
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// Zip archive entries.
-//
-// The ZipEntry class is tightly meshed with the ZipFile class.
-//
-#ifndef __LIBS_ZIPENTRY_H
-#define __LIBS_ZIPENTRY_H
-
-#include <utils/Errors.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-
-namespace aapt {
-
-using android::status_t;
-
-class ZipFile;
-
-/*
- * ZipEntry objects represent a single entry in a Zip archive.
- *
- * You can use one of these to get or set information about an entry, but
- * there are no functions here for accessing the data itself.  (We could
- * tuck a pointer to the ZipFile in here for convenience, but that raises
- * the likelihood of using ZipEntry objects after discarding the ZipFile.)
- *
- * File information is stored in two places: next to the file data (the Local
- * File Header, and possibly a Data Descriptor), and at the end of the file
- * (the Central Directory Entry).  The two must be kept in sync.
- */
-class ZipEntry {
-public:
-    friend class ZipFile;
-
-    ZipEntry(void)
-        : mDeleted(false), mMarked(false)
-        {}
-    ~ZipEntry(void) {}
-
-    /*
-     * Returns "true" if the data is compressed.
-     */
-    bool isCompressed(void) const {
-        return mCDE.mCompressionMethod != kCompressStored;
-    }
-    int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
-
-    /*
-     * Return the uncompressed length.
-     */
-    off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
-
-    /*
-     * Return the compressed length.  For uncompressed data, this returns
-     * the same thing as getUncompresesdLen().
-     */
-    off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
-
-    /*
-     * Return the offset of the local file header.
-     */
-    off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
-
-    /*
-     * Return the absolute file offset of the start of the compressed or
-     * uncompressed data.
-     */
-    off_t getFileOffset(void) const {
-        return mCDE.mLocalHeaderRelOffset +
-                LocalFileHeader::kLFHLen +
-                mLFH.mFileNameLength +
-                mLFH.mExtraFieldLength;
-    }
-
-    /*
-     * Return the data CRC.
-     */
-    unsigned long getCRC32(void) const { return mCDE.mCRC32; }
-
-    /*
-     * Return file modification time in UNIX seconds-since-epoch.
-     */
-    time_t getModWhen(void) const;
-
-    /*
-     * Return the archived file name.
-     */
-    const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
-
-    /*
-     * Application-defined "mark".  Can be useful when synchronizing the
-     * contents of an archive with contents on disk.
-     */
-    bool getMarked(void) const { return mMarked; }
-    void setMarked(bool val) { mMarked = val; }
-
-    /*
-     * Some basic functions for raw data manipulation.  "LE" means
-     * Little Endian.
-     */
-    static inline unsigned short getShortLE(const unsigned char* buf) {
-        return buf[0] | (buf[1] << 8);
-    }
-    static inline unsigned long getLongLE(const unsigned char* buf) {
-        return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
-    }
-    static inline void putShortLE(unsigned char* buf, short val) {
-        buf[0] = (unsigned char) val;
-        buf[1] = (unsigned char) (val >> 8);
-    }
-    static inline void putLongLE(unsigned char* buf, long val) {
-        buf[0] = (unsigned char) val;
-        buf[1] = (unsigned char) (val >> 8);
-        buf[2] = (unsigned char) (val >> 16);
-        buf[3] = (unsigned char) (val >> 24);
-    }
-
-    /* defined for Zip archives */
-    enum {
-        kCompressStored     = 0,        // no compression
-        // shrunk           = 1,
-        // reduced 1        = 2,
-        // reduced 2        = 3,
-        // reduced 3        = 4,
-        // reduced 4        = 5,
-        // imploded         = 6,
-        // tokenized        = 7,
-        kCompressDeflated   = 8,        // standard deflate
-        // Deflate64        = 9,
-        // lib imploded     = 10,
-        // reserved         = 11,
-        // bzip2            = 12,
-    };
-
-    /*
-     * Deletion flag.  If set, the entry will be removed on the next
-     * call to "flush".
-     */
-    bool getDeleted(void) const { return mDeleted; }
-
-protected:
-    /*
-     * Initialize the structure from the file, which is pointing at
-     * our Central Directory entry.
-     */
-    status_t initFromCDE(FILE* fp);
-
-    /*
-     * Initialize the structure for a new file.  We need the filename
-     * and comment so that we can properly size the LFH area.  The
-     * filename is mandatory, the comment is optional.
-     */
-    void initNew(const char* fileName, const char* comment);
-
-    /*
-     * Initialize the structure with the contents of a ZipEntry from
-     * another file. If fileName is non-NULL, override the name with fileName.
-     */
-    status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry,
-                              const char* fileName);
-
-    /*
-     * Add some pad bytes to the LFH.  We do this by adding or resizing
-     * the "extra" field.
-     */
-    status_t addPadding(int padding);
-
-    /*
-     * Set information about the data for this entry.
-     */
-    void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
-        int compressionMethod);
-
-    /*
-     * Set the modification date.
-     */
-    void setModWhen(time_t when);
-
-    /*
-     * Set the offset of the local file header, relative to the start of
-     * the current file.
-     */
-    void setLFHOffset(off_t offset) {
-        mCDE.mLocalHeaderRelOffset = (long) offset;
-    }
-
-    /* mark for deletion; used by ZipFile::remove() */
-    void setDeleted(void) { mDeleted = true; }
-
-private:
-    /* these are private and not defined */
-    ZipEntry(const ZipEntry& src);
-    ZipEntry& operator=(const ZipEntry& src);
-
-    /* returns "true" if the CDE and the LFH agree */
-    bool compareHeaders(void) const;
-    void copyCDEtoLFH(void);
-
-    bool        mDeleted;       // set if entry is pending deletion
-    bool        mMarked;        // app-defined marker
-
-    /*
-     * Every entry in the Zip archive starts off with one of these.
-     */
-    class LocalFileHeader {
-    public:
-        LocalFileHeader(void) :
-            mVersionToExtract(0),
-            mGPBitFlag(0),
-            mCompressionMethod(0),
-            mLastModFileTime(0),
-            mLastModFileDate(0),
-            mCRC32(0),
-            mCompressedSize(0),
-            mUncompressedSize(0),
-            mFileNameLength(0),
-            mExtraFieldLength(0),
-            mFileName(NULL),
-            mExtraField(NULL)
-        {}
-        virtual ~LocalFileHeader(void) {
-            delete[] mFileName;
-            delete[] mExtraField;
-        }
-
-        status_t read(FILE* fp);
-        status_t write(FILE* fp);
-
-        // unsigned long mSignature;
-        unsigned short  mVersionToExtract;
-        unsigned short  mGPBitFlag;
-        unsigned short  mCompressionMethod;
-        unsigned short  mLastModFileTime;
-        unsigned short  mLastModFileDate;
-        unsigned long   mCRC32;
-        unsigned long   mCompressedSize;
-        unsigned long   mUncompressedSize;
-        unsigned short  mFileNameLength;
-        unsigned short  mExtraFieldLength;
-        unsigned char*  mFileName;
-        unsigned char*  mExtraField;
-
-        enum {
-            kSignature      = 0x04034b50,
-            kLFHLen         = 30,       // LocalFileHdr len, excl. var fields
-        };
-
-        void dump(void) const;
-    };
-
-    /*
-     * Every entry in the Zip archive has one of these in the "central
-     * directory" at the end of the file.
-     */
-    class CentralDirEntry {
-    public:
-        CentralDirEntry(void) :
-            mVersionMadeBy(0),
-            mVersionToExtract(0),
-            mGPBitFlag(0),
-            mCompressionMethod(0),
-            mLastModFileTime(0),
-            mLastModFileDate(0),
-            mCRC32(0),
-            mCompressedSize(0),
-            mUncompressedSize(0),
-            mFileNameLength(0),
-            mExtraFieldLength(0),
-            mFileCommentLength(0),
-            mDiskNumberStart(0),
-            mInternalAttrs(0),
-            mExternalAttrs(0),
-            mLocalHeaderRelOffset(0),
-            mFileName(NULL),
-            mExtraField(NULL),
-            mFileComment(NULL)
-        {}
-        virtual ~CentralDirEntry(void) {
-            delete[] mFileName;
-            delete[] mExtraField;
-            delete[] mFileComment;
-        }
-
-        status_t read(FILE* fp);
-        status_t write(FILE* fp);
-
-        CentralDirEntry& operator=(const CentralDirEntry& src);
-
-        // unsigned long mSignature;
-        unsigned short  mVersionMadeBy;
-        unsigned short  mVersionToExtract;
-        unsigned short  mGPBitFlag;
-        unsigned short  mCompressionMethod;
-        unsigned short  mLastModFileTime;
-        unsigned short  mLastModFileDate;
-        unsigned long   mCRC32;
-        unsigned long   mCompressedSize;
-        unsigned long   mUncompressedSize;
-        unsigned short  mFileNameLength;
-        unsigned short  mExtraFieldLength;
-        unsigned short  mFileCommentLength;
-        unsigned short  mDiskNumberStart;
-        unsigned short  mInternalAttrs;
-        unsigned long   mExternalAttrs;
-        unsigned long   mLocalHeaderRelOffset;
-        unsigned char*  mFileName;
-        unsigned char*  mExtraField;
-        unsigned char*  mFileComment;
-
-        void dump(void) const;
-
-        enum {
-            kSignature      = 0x02014b50,
-            kCDELen         = 46,       // CentralDirEnt len, excl. var fields
-        };
-    };
-
-    enum {
-        //kDataDescriptorSignature  = 0x08074b50,   // currently unused
-        kDataDescriptorLen  = 16,           // four 32-bit fields
-
-        kDefaultVersion     = 20,           // need deflate, nothing much else
-        kDefaultMadeBy      = 0x0317,       // 03=UNIX, 17=spec v2.3
-        kUsesDataDescr      = 0x0008,       // GPBitFlag bit 3
-    };
-
-    LocalFileHeader     mLFH;
-    CentralDirEntry     mCDE;
-};
-
-}; // namespace aapt
-
-#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp
deleted file mode 100644
index 268c15e..0000000
--- a/tools/aapt2/ZipFile.cpp
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// Access to Zip archives.
-//
-
-#define LOG_TAG "zip"
-
-#include <androidfw/ZipUtils.h>
-#include <utils/Log.h>
-
-#include "ZipFile.h"
-#include "Util.h"
-
-#include <zlib.h>
-#define DEF_MEM_LEVEL 8                // normally in zutil.h?
-
-#include <memory.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <assert.h>
-
-namespace aapt {
-
-using namespace android;
-
-/*
- * Some environments require the "b", some choke on it.
- */
-#define FILE_OPEN_RO        "rb"
-#define FILE_OPEN_RW        "r+b"
-#define FILE_OPEN_RW_CREATE "w+b"
-
-/* should live somewhere else? */
-static status_t errnoToStatus(int err)
-{
-    if (err == ENOENT)
-        return NAME_NOT_FOUND;
-    else if (err == EACCES)
-        return PERMISSION_DENIED;
-    else
-        return UNKNOWN_ERROR;
-}
-
-/*
- * Open a file and parse its guts.
- */
-status_t ZipFile::open(const char* zipFileName, int flags)
-{
-    bool newArchive = false;
-
-    assert(mZipFp == NULL);     // no reopen
-
-    if ((flags & kOpenTruncate))
-        flags |= kOpenCreate;           // trunc implies create
-
-    if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
-        return INVALID_OPERATION;       // not both
-    if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
-        return INVALID_OPERATION;       // not neither
-    if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
-        return INVALID_OPERATION;       // create requires write
-
-    if (flags & kOpenTruncate) {
-        newArchive = true;
-    } else {
-        newArchive = (access(zipFileName, F_OK) != 0);
-        if (!(flags & kOpenCreate) && newArchive) {
-            /* not creating, must already exist */
-            ALOGD("File %s does not exist", zipFileName);
-            return NAME_NOT_FOUND;
-        }
-    }
-
-    /* open the file */
-    const char* openflags;
-    if (flags & kOpenReadWrite) {
-        if (newArchive)
-            openflags = FILE_OPEN_RW_CREATE;
-        else
-            openflags = FILE_OPEN_RW;
-    } else {
-        openflags = FILE_OPEN_RO;
-    }
-    mZipFp = fopen(zipFileName, openflags);
-    if (mZipFp == NULL) {
-        int err = errno;
-        ALOGD("fopen failed: %d\n", err);
-        return errnoToStatus(err);
-    }
-
-    status_t result;
-    if (!newArchive) {
-        /*
-         * Load the central directory.  If that fails, then this probably
-         * isn't a Zip archive.
-         */
-        result = readCentralDir();
-    } else {
-        /*
-         * Newly-created.  The EndOfCentralDir constructor actually
-         * sets everything to be the way we want it (all zeroes).  We
-         * set mNeedCDRewrite so that we create *something* if the
-         * caller doesn't add any files.  (We could also just unlink
-         * the file if it's brand new and nothing was added, but that's
-         * probably doing more than we really should -- the user might
-         * have a need for empty zip files.)
-         */
-        mNeedCDRewrite = true;
-        result = NO_ERROR;
-    }
-
-    if (flags & kOpenReadOnly)
-        mReadOnly = true;
-    else
-        assert(!mReadOnly);
-
-    return result;
-}
-
-/*
- * Return the Nth entry in the archive.
- */
-ZipEntry* ZipFile::getEntryByIndex(int idx) const
-{
-    if (idx < 0 || idx >= (int) mEntries.size())
-        return NULL;
-
-    return mEntries[idx];
-}
-
-/*
- * Find an entry by name.
- */
-ZipEntry* ZipFile::getEntryByName(const char* fileName) const
-{
-    /*
-     * Do a stupid linear string-compare search.
-     *
-     * There are various ways to speed this up, especially since it's rare
-     * to intermingle changes to the archive with "get by name" calls.  We
-     * don't want to sort the mEntries vector itself, however, because
-     * it's used to recreate the Central Directory.
-     *
-     * (Hash table works, parallel list of pointers in sorted order is good.)
-     */
-    int idx;
-
-    for (idx = mEntries.size()-1; idx >= 0; idx--) {
-        ZipEntry* pEntry = mEntries[idx];
-        if (!pEntry->getDeleted() &&
-            strcmp(fileName, pEntry->getFileName()) == 0)
-        {
-            return pEntry;
-        }
-    }
-
-    return NULL;
-}
-
-/*
- * Empty the mEntries vector.
- */
-void ZipFile::discardEntries(void)
-{
-    int count = mEntries.size();
-
-    while (--count >= 0)
-        delete mEntries[count];
-
-    mEntries.clear();
-}
-
-
-/*
- * Find the central directory and read the contents.
- *
- * The fun thing about ZIP archives is that they may or may not be
- * readable from start to end.  In some cases, notably for archives
- * that were written to stdout, the only length information is in the
- * central directory at the end of the file.
- *
- * Of course, the central directory can be followed by a variable-length
- * comment field, so we have to scan through it backwards.  The comment
- * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
- * itself, plus apparently sometimes people throw random junk on the end
- * just for the fun of it.
- *
- * This is all a little wobbly.  If the wrong value ends up in the EOCD
- * area, we're hosed.  This appears to be the way that everbody handles
- * it though, so we're in pretty good company if this fails.
- */
-status_t ZipFile::readCentralDir(void)
-{
-    status_t result = NO_ERROR;
-    unsigned char* buf = NULL;
-    off_t fileLength, seekStart;
-    long readAmount;
-    int i;
-
-    fseek(mZipFp, 0, SEEK_END);
-    fileLength = ftell(mZipFp);
-    rewind(mZipFp);
-
-    /* too small to be a ZIP archive? */
-    if (fileLength < EndOfCentralDir::kEOCDLen) {
-        ALOGD("Length is %ld -- too small\n", (long)fileLength);
-        result = INVALID_OPERATION;
-        goto bail;
-    }
-
-    buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
-    if (buf == NULL) {
-        ALOGD("Failure allocating %d bytes for EOCD search",
-             EndOfCentralDir::kMaxEOCDSearch);
-        result = NO_MEMORY;
-        goto bail;
-    }
-
-    if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
-        seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
-        readAmount = EndOfCentralDir::kMaxEOCDSearch;
-    } else {
-        seekStart = 0;
-        readAmount = (long) fileLength;
-    }
-    if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
-        ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /* read the last part of the file into the buffer */
-    if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
-        ALOGD("short file? wanted %ld\n", readAmount);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /* find the end-of-central-dir magic */
-    for (i = readAmount - 4; i >= 0; i--) {
-        if (buf[i] == 0x50 &&
-            ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
-        {
-            ALOGV("+++ Found EOCD at buf+%d\n", i);
-            break;
-        }
-    }
-    if (i < 0) {
-        ALOGD("EOCD not found, not Zip\n");
-        result = INVALID_OPERATION;
-        goto bail;
-    }
-
-    /* extract eocd values */
-    result = mEOCD.readBuf(buf + i, readAmount - i);
-    if (result != NO_ERROR) {
-        ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
-        goto bail;
-    }
-    //mEOCD.dump();
-
-    if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
-        mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
-    {
-        ALOGD("Archive spanning not supported\n");
-        result = INVALID_OPERATION;
-        goto bail;
-    }
-
-    /*
-     * So far so good.  "mCentralDirSize" is the size in bytes of the
-     * central directory, so we can just seek back that far to find it.
-     * We can also seek forward mCentralDirOffset bytes from the
-     * start of the file.
-     *
-     * We're not guaranteed to have the rest of the central dir in the
-     * buffer, nor are we guaranteed that the central dir will have any
-     * sort of convenient size.  We need to skip to the start of it and
-     * read the header, then the other goodies.
-     *
-     * The only thing we really need right now is the file comment, which
-     * we're hoping to preserve.
-     */
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
-        ALOGD("Failure seeking to central dir offset %ld\n",
-             mEOCD.mCentralDirOffset);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /*
-     * Loop through and read the central dir entries.
-     */
-    ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
-    int entry;
-    for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
-        ZipEntry* pEntry = new ZipEntry;
-
-        result = pEntry->initFromCDE(mZipFp);
-        if (result != NO_ERROR) {
-            ALOGD("initFromCDE failed\n");
-            delete pEntry;
-            goto bail;
-        }
-
-        mEntries.push_back(pEntry);
-    }
-
-
-    /*
-     * If all went well, we should now be back at the EOCD.
-     */
-    {
-        unsigned char checkBuf[4];
-        if (fread(checkBuf, 1, 4, mZipFp) != 4) {
-            ALOGD("EOCD check read failed\n");
-            result = INVALID_OPERATION;
-            goto bail;
-        }
-        if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
-            ALOGD("EOCD read check failed\n");
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-        ALOGV("+++ EOCD read check passed\n");
-    }
-
-bail:
-    delete[] buf;
-    return result;
-}
-
-status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod,
-                      ZipEntry** ppEntry) {
-    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
-    return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry);
-}
-
-
-/*
- * Add a new file to the archive.
- *
- * This requires creating and populating a ZipEntry structure, and copying
- * the data into the file at the appropriate position.  The "appropriate
- * position" is the current location of the central directory, which we
- * casually overwrite (we can put it back later).
- *
- * If we were concerned about safety, we would want to make all changes
- * in a temp file and then overwrite the original after everything was
- * safely written.  Not really a concern for us.
- */
-status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
-    const char* storageName, int sourceType, int compressionMethod,
-    ZipEntry** ppEntry)
-{
-    ZipEntry* pEntry = NULL;
-    status_t result = NO_ERROR;
-    long lfhPosn, startPosn, endPosn, uncompressedLen;
-    FILE* inputFp = NULL;
-    unsigned long crc;
-    time_t modWhen;
-
-    if (mReadOnly)
-        return INVALID_OPERATION;
-
-    assert(compressionMethod == ZipEntry::kCompressDeflated ||
-           compressionMethod == ZipEntry::kCompressStored);
-
-    /* make sure we're in a reasonable state */
-    assert(mZipFp != NULL);
-    assert(mEntries.size() == mEOCD.mTotalNumEntries);
-
-    /* make sure it doesn't already exist */
-    if (getEntryByName(storageName) != NULL)
-        return ALREADY_EXISTS;
-
-    if (!data) {
-        inputFp = fopen(fileName, FILE_OPEN_RO);
-        if (inputFp == NULL)
-            return errnoToStatus(errno);
-    }
-
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    pEntry = new ZipEntry;
-    pEntry->initNew(storageName, NULL);
-
-    /*
-     * From here on out, failures are more interesting.
-     */
-    mNeedCDRewrite = true;
-
-    /*
-     * Write the LFH, even though it's still mostly blank.  We need it
-     * as a place-holder.  In theory the LFH isn't necessary, but in
-     * practice some utilities demand it.
-     */
-    lfhPosn = ftell(mZipFp);
-    pEntry->mLFH.write(mZipFp);
-    startPosn = ftell(mZipFp);
-
-    /*
-     * Copy the data in, possibly compressing it as we go.
-     */
-    if (sourceType == ZipEntry::kCompressStored) {
-        if (compressionMethod == ZipEntry::kCompressDeflated) {
-            bool failed = false;
-            result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
-            if (result != NO_ERROR) {
-                ALOGD("compression failed, storing\n");
-                failed = true;
-            } else {
-                /*
-                 * Make sure it has compressed "enough".  This probably ought
-                 * to be set through an API call, but I don't expect our
-                 * criteria to change over time.
-                 */
-                long src = inputFp ? ftell(inputFp) : size;
-                long dst = ftell(mZipFp) - startPosn;
-                if (dst + (dst / 10) > src) {
-                    ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
-                        src, dst);
-                    failed = true;
-                }
-            }
-
-            if (failed) {
-                compressionMethod = ZipEntry::kCompressStored;
-                if (inputFp) rewind(inputFp);
-                fseek(mZipFp, startPosn, SEEK_SET);
-                /* fall through to kCompressStored case */
-            }
-        }
-        /* handle "no compression" request, or failed compression from above */
-        if (compressionMethod == ZipEntry::kCompressStored) {
-            if (inputFp) {
-                result = copyFpToFp(mZipFp, inputFp, &crc);
-            } else {
-                result = copyDataToFp(mZipFp, data, size, &crc);
-            }
-            if (result != NO_ERROR) {
-                // don't need to truncate; happens in CDE rewrite
-                ALOGD("failed copying data in\n");
-                goto bail;
-            }
-        }
-
-        // currently seeked to end of file
-        uncompressedLen = inputFp ? ftell(inputFp) : size;
-    } else if (sourceType == ZipEntry::kCompressDeflated) {
-        /* we should support uncompressed-from-compressed, but it's not
-         * important right now */
-        assert(compressionMethod == ZipEntry::kCompressDeflated);
-
-        bool scanResult;
-        int method;
-        long compressedLen;
-
-        scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
-                        &compressedLen, &crc);
-        if (!scanResult || method != ZipEntry::kCompressDeflated) {
-            ALOGD("this isn't a deflated gzip file?");
-            result = UNKNOWN_ERROR;
-            goto bail;
-        }
-
-        result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
-        if (result != NO_ERROR) {
-            ALOGD("failed copying gzip data in\n");
-            goto bail;
-        }
-    } else {
-        assert(false);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /*
-     * We could write the "Data Descriptor", but there doesn't seem to
-     * be any point since we're going to go back and write the LFH.
-     *
-     * Update file offsets.
-     */
-    endPosn = ftell(mZipFp);            // seeked to end of compressed data
-
-    /*
-     * Success!  Fill out new values.
-     */
-    pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
-        compressionMethod);
-    modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
-    pEntry->setModWhen(modWhen);
-    pEntry->setLFHOffset(lfhPosn);
-    mEOCD.mNumEntries++;
-    mEOCD.mTotalNumEntries++;
-    mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
-    mEOCD.mCentralDirOffset = endPosn;
-
-    /*
-     * Go back and write the LFH.
-     */
-    if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-    pEntry->mLFH.write(mZipFp);
-
-    /*
-     * Add pEntry to the list.
-     */
-    mEntries.push_back(pEntry);
-    if (ppEntry != NULL)
-        *ppEntry = pEntry;
-    pEntry = NULL;
-
-bail:
-    if (inputFp != NULL)
-        fclose(inputFp);
-    delete pEntry;
-    return result;
-}
-
-/*
- * Add an entry by copying it from another zip file.  If "padding" is
- * nonzero, the specified number of bytes will be added to the "extra"
- * field in the header.
- *
- * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
- */
-status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
-                      const char* storageName, int padding, ZipEntry** ppEntry)
-{
-    ZipEntry* pEntry = NULL;
-    status_t result;
-    long lfhPosn, endPosn;
-
-    if (mReadOnly)
-        return INVALID_OPERATION;
-
-    /* make sure we're in a reasonable state */
-    assert(mZipFp != NULL);
-    assert(mEntries.size() == mEOCD.mTotalNumEntries);
-
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    pEntry = new ZipEntry;
-    if (pEntry == NULL) {
-        result = NO_MEMORY;
-        goto bail;
-    }
-
-    result = pEntry->initFromExternal(pSourceZip, pSourceEntry, storageName);
-    if (result != NO_ERROR) {
-        goto bail;
-    }
-    if (padding != 0) {
-        result = pEntry->addPadding(padding);
-        if (result != NO_ERROR)
-            goto bail;
-    }
-
-    /*
-     * From here on out, failures are more interesting.
-     */
-    mNeedCDRewrite = true;
-
-    /*
-     * Write the LFH.  Since we're not recompressing the data, we already
-     * have all of the fields filled out.
-     */
-    lfhPosn = ftell(mZipFp);
-    pEntry->mLFH.write(mZipFp);
-
-    /*
-     * Copy the data over.
-     *
-     * If the "has data descriptor" flag is set, we want to copy the DD
-     * fields as well.  This is a fixed-size area immediately following
-     * the data.
-     */
-    if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
-    {
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    off_t copyLen;
-    copyLen = pSourceEntry->getCompressedLen();
-    if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
-        copyLen += ZipEntry::kDataDescriptorLen;
-
-    if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
-        != NO_ERROR)
-    {
-        ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
-        result = UNKNOWN_ERROR;
-        goto bail;
-    }
-
-    /*
-     * Update file offsets.
-     */
-    endPosn = ftell(mZipFp);
-
-    /*
-     * Success!  Fill out new values.
-     */
-    pEntry->setLFHOffset(lfhPosn);      // sets mCDE.mLocalHeaderRelOffset
-    mEOCD.mNumEntries++;
-    mEOCD.mTotalNumEntries++;
-    mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
-    mEOCD.mCentralDirOffset = endPosn;
-
-    /*
-     * Add pEntry to the list.
-     */
-    mEntries.push_back(pEntry);
-    if (ppEntry != NULL)
-        *ppEntry = pEntry;
-    pEntry = NULL;
-
-    result = NO_ERROR;
-
-bail:
-    delete pEntry;
-    return result;
-}
-
-/*
- * Copy all of the bytes in "src" to "dst".
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the data.
- */
-status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
-{
-    unsigned char tmpBuf[32768];
-    size_t count;
-
-    *pCRC32 = crc32(0L, Z_NULL, 0);
-
-    while (1) {
-        count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
-        if (ferror(srcFp) || ferror(dstFp))
-            return errnoToStatus(errno);
-        if (count == 0)
-            break;
-
-        *pCRC32 = crc32(*pCRC32, tmpBuf, count);
-
-        if (fwrite(tmpBuf, 1, count, dstFp) != count) {
-            ALOGD("fwrite %d bytes failed\n", (int) count);
-            return UNKNOWN_ERROR;
-        }
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Copy all of the bytes in "src" to "dst".
- *
- * On exit, "dstFp" will be seeked immediately past the data.
- */
-status_t ZipFile::copyDataToFp(FILE* dstFp,
-    const void* data, size_t size, unsigned long* pCRC32)
-{
-    *pCRC32 = crc32(0L, Z_NULL, 0);
-    if (size > 0) {
-        *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
-        if (fwrite(data, 1, size, dstFp) != size) {
-            ALOGD("fwrite %d bytes failed\n", (int) size);
-            return UNKNOWN_ERROR;
-        }
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Copy some of the bytes in "src" to "dst".
- *
- * If "pCRC32" is NULL, the CRC will not be computed.
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the data just written.
- */
-status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
-    unsigned long* pCRC32)
-{
-    unsigned char tmpBuf[32768];
-    size_t count;
-
-    if (pCRC32 != NULL)
-        *pCRC32 = crc32(0L, Z_NULL, 0);
-
-    while (length) {
-        long readSize;
-
-        readSize = sizeof(tmpBuf);
-        if (readSize > length)
-            readSize = length;
-
-        count = fread(tmpBuf, 1, readSize, srcFp);
-        if ((long) count != readSize) {     // error or unexpected EOF
-            ALOGD("fread %d bytes failed\n", (int) readSize);
-            return UNKNOWN_ERROR;
-        }
-
-        if (pCRC32 != NULL)
-            *pCRC32 = crc32(*pCRC32, tmpBuf, count);
-
-        if (fwrite(tmpBuf, 1, count, dstFp) != count) {
-            ALOGD("fwrite %d bytes failed\n", (int) count);
-            return UNKNOWN_ERROR;
-        }
-
-        length -= readSize;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Compress all of the data in "srcFp" and write it to "dstFp".
- *
- * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
- * will be seeked immediately past the compressed data.
- */
-status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
-    const void* data, size_t size, unsigned long* pCRC32)
-{
-    status_t result = NO_ERROR;
-    const size_t kBufSize = 32768;
-    unsigned char* inBuf = NULL;
-    unsigned char* outBuf = NULL;
-    z_stream zstream;
-    bool atEof = false;     // no feof() aviailable yet
-    unsigned long crc;
-    int zerr;
-
-    /*
-     * Create an input buffer and an output buffer.
-     */
-    inBuf = new unsigned char[kBufSize];
-    outBuf = new unsigned char[kBufSize];
-    if (inBuf == NULL || outBuf == NULL) {
-        result = NO_MEMORY;
-        goto bail;
-    }
-
-    /*
-     * Initialize the zlib stream.
-     */
-    memset(&zstream, 0, sizeof(zstream));
-    zstream.zalloc = Z_NULL;
-    zstream.zfree = Z_NULL;
-    zstream.opaque = Z_NULL;
-    zstream.next_in = NULL;
-    zstream.avail_in = 0;
-    zstream.next_out = outBuf;
-    zstream.avail_out = kBufSize;
-    zstream.data_type = Z_UNKNOWN;
-
-    zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
-        Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
-    if (zerr != Z_OK) {
-        result = UNKNOWN_ERROR;
-        if (zerr == Z_VERSION_ERROR) {
-            ALOGE("Installed zlib is not compatible with linked version (%s)\n",
-                ZLIB_VERSION);
-        } else {
-            ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
-        }
-        goto bail;
-    }
-
-    crc = crc32(0L, Z_NULL, 0);
-
-    /*
-     * Loop while we have data.
-     */
-    do {
-        size_t getSize;
-        int flush;
-
-        /* only read if the input buffer is empty */
-        if (zstream.avail_in == 0 && !atEof) {
-            ALOGV("+++ reading %d bytes\n", (int)kBufSize);
-            if (data) {
-                getSize = size > kBufSize ? kBufSize : size;
-                memcpy(inBuf, data, getSize);
-                data = ((const char*)data) + getSize;
-                size -= getSize;
-            } else {
-                getSize = fread(inBuf, 1, kBufSize, srcFp);
-                if (ferror(srcFp)) {
-                    ALOGD("deflate read failed (errno=%d)\n", errno);
-                    goto z_bail;
-                }
-            }
-            if (getSize < kBufSize) {
-                ALOGV("+++  got %d bytes, EOF reached\n",
-                    (int)getSize);
-                atEof = true;
-            }
-
-            crc = crc32(crc, inBuf, getSize);
-
-            zstream.next_in = inBuf;
-            zstream.avail_in = getSize;
-        }
-
-        if (atEof)
-            flush = Z_FINISH;       /* tell zlib that we're done */
-        else
-            flush = Z_NO_FLUSH;     /* more to come! */
-
-        zerr = deflate(&zstream, flush);
-        if (zerr != Z_OK && zerr != Z_STREAM_END) {
-            ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
-            result = UNKNOWN_ERROR;
-            goto z_bail;
-        }
-
-        /* write when we're full or when we're done */
-        if (zstream.avail_out == 0 ||
-            (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
-        {
-            ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
-            if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
-                (size_t)(zstream.next_out - outBuf))
-            {
-                ALOGD("write %d failed in deflate\n",
-                    (int) (zstream.next_out - outBuf));
-                goto z_bail;
-            }
-
-            zstream.next_out = outBuf;
-            zstream.avail_out = kBufSize;
-        }
-    } while (zerr == Z_OK);
-
-    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
-
-    *pCRC32 = crc;
-
-z_bail:
-    deflateEnd(&zstream);        /* free up any allocated structures */
-
-bail:
-    delete[] inBuf;
-    delete[] outBuf;
-
-    return result;
-}
-
-/*
- * Mark an entry as deleted.
- *
- * We will eventually need to crunch the file down, but if several files
- * are being removed (perhaps as part of an "update" process) we can make
- * things considerably faster by deferring the removal to "flush" time.
- */
-status_t ZipFile::remove(ZipEntry* pEntry)
-{
-    /*
-     * Should verify that pEntry is actually part of this archive, and
-     * not some stray ZipEntry from a different file.
-     */
-
-    /* mark entry as deleted, and mark archive as dirty */
-    pEntry->setDeleted();
-    mNeedCDRewrite = true;
-    return NO_ERROR;
-}
-
-/*
- * Flush any pending writes.
- *
- * In particular, this will crunch out deleted entries, and write the
- * Central Directory and EOCD if we have stomped on them.
- */
-status_t ZipFile::flush(void)
-{
-    status_t result = NO_ERROR;
-    long eocdPosn;
-    int i, count;
-
-    if (mReadOnly)
-        return INVALID_OPERATION;
-    if (!mNeedCDRewrite)
-        return NO_ERROR;
-
-    assert(mZipFp != NULL);
-
-    result = crunchArchive();
-    if (result != NO_ERROR)
-        return result;
-
-    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
-        return UNKNOWN_ERROR;
-
-    count = mEntries.size();
-    for (i = 0; i < count; i++) {
-        ZipEntry* pEntry = mEntries[i];
-        pEntry->mCDE.write(mZipFp);
-    }
-
-    eocdPosn = ftell(mZipFp);
-    mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
-
-    mEOCD.write(mZipFp);
-
-    /*
-     * If we had some stuff bloat up during compression and get replaced
-     * with plain files, or if we deleted some entries, there's a lot
-     * of wasted space at the end of the file.  Remove it now.
-     */
-    if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
-        ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
-        // not fatal
-    }
-
-    /* should we clear the "newly added" flag in all entries now? */
-
-    mNeedCDRewrite = false;
-    return NO_ERROR;
-}
-
-/*
- * Crunch deleted files out of an archive by shifting the later files down.
- *
- * Because we're not using a temp file, we do the operation inside the
- * current file.
- */
-status_t ZipFile::crunchArchive(void)
-{
-    status_t result = NO_ERROR;
-    int i, count;
-    long delCount, adjust;
-
-#if 0
-    printf("CONTENTS:\n");
-    for (i = 0; i < (int) mEntries.size(); i++) {
-        printf(" %d: lfhOff=%ld del=%d\n",
-            i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
-    }
-    printf("  END is %ld\n", (long) mEOCD.mCentralDirOffset);
-#endif
-
-    /*
-     * Roll through the set of files, shifting them as appropriate.  We
-     * could probably get a slight performance improvement by sliding
-     * multiple files down at once (because we could use larger reads
-     * when operating on batches of small files), but it's not that useful.
-     */
-    count = mEntries.size();
-    delCount = adjust = 0;
-    for (i = 0; i < count; i++) {
-        ZipEntry* pEntry = mEntries[i];
-        long span;
-
-        if (pEntry->getLFHOffset() != 0) {
-            long nextOffset;
-
-            /* Get the length of this entry by finding the offset
-             * of the next entry.  Directory entries don't have
-             * file offsets, so we need to find the next non-directory
-             * entry.
-             */
-            nextOffset = 0;
-            for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
-                nextOffset = mEntries[ii]->getLFHOffset();
-            if (nextOffset == 0)
-                nextOffset = mEOCD.mCentralDirOffset;
-            span = nextOffset - pEntry->getLFHOffset();
-
-            assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
-        } else {
-            /* This is a directory entry.  It doesn't have
-             * any actual file contents, so there's no need to
-             * move anything.
-             */
-            span = 0;
-        }
-
-        //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
-        //    i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
-
-        if (pEntry->getDeleted()) {
-            adjust += span;
-            delCount++;
-
-            delete pEntry;
-            mEntries.erase(mEntries.begin() + i);
-
-            /* adjust loop control */
-            count--;
-            i--;
-        } else if (span != 0 && adjust > 0) {
-            /* shuffle this entry back */
-            //printf("+++ Shuffling '%s' back %ld\n",
-            //    pEntry->getFileName(), adjust);
-            result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
-                        pEntry->getLFHOffset(), span);
-            if (result != NO_ERROR) {
-                /* this is why you use a temp file */
-                ALOGE("error during crunch - archive is toast\n");
-                return result;
-            }
-
-            pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
-        }
-    }
-
-    /*
-     * Fix EOCD info.  We have to wait until the end to do some of this
-     * because we use mCentralDirOffset to determine "span" for the
-     * last entry.
-     */
-    mEOCD.mCentralDirOffset -= adjust;
-    mEOCD.mNumEntries -= delCount;
-    mEOCD.mTotalNumEntries -= delCount;
-    mEOCD.mCentralDirSize = 0;  // mark invalid; set by flush()
-
-    assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
-    assert(mEOCD.mNumEntries == count);
-
-    return result;
-}
-
-/*
- * Works like memmove(), but on pieces of a file.
- */
-status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
-{
-    if (dst == src || n <= 0)
-        return NO_ERROR;
-
-    unsigned char readBuf[32768];
-
-    if (dst < src) {
-        /* shift stuff toward start of file; must read from start */
-        while (n != 0) {
-            size_t getSize = sizeof(readBuf);
-            if (getSize > n)
-                getSize = n;
-
-            if (fseek(fp, (long) src, SEEK_SET) != 0) {
-                ALOGD("filemove src seek %ld failed\n", (long) src);
-                return UNKNOWN_ERROR;
-            }
-
-            if (fread(readBuf, 1, getSize, fp) != getSize) {
-                ALOGD("filemove read %ld off=%ld failed\n",
-                    (long) getSize, (long) src);
-                return UNKNOWN_ERROR;
-            }
-
-            if (fseek(fp, (long) dst, SEEK_SET) != 0) {
-                ALOGD("filemove dst seek %ld failed\n", (long) dst);
-                return UNKNOWN_ERROR;
-            }
-
-            if (fwrite(readBuf, 1, getSize, fp) != getSize) {
-                ALOGD("filemove write %ld off=%ld failed\n",
-                    (long) getSize, (long) dst);
-                return UNKNOWN_ERROR;
-            }
-
-            src += getSize;
-            dst += getSize;
-            n -= getSize;
-        }
-    } else {
-        /* shift stuff toward end of file; must read from end */
-        assert(false);      // write this someday, maybe
-        return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-
-/*
- * Get the modification time from a file descriptor.
- */
-time_t ZipFile::getModTime(int fd)
-{
-    struct stat sb;
-
-    if (fstat(fd, &sb) < 0) {
-        ALOGD("HEY: fstat on fd %d failed\n", fd);
-        return (time_t) -1;
-    }
-
-    return sb.st_mtime;
-}
-
-
-#if 0       /* this is a bad idea */
-/*
- * Get a copy of the Zip file descriptor.
- *
- * We don't allow this if the file was opened read-write because we tend
- * to leave the file contents in an uncertain state between calls to
- * flush().  The duplicated file descriptor should only be valid for reads.
- */
-int ZipFile::getZipFd(void) const
-{
-    if (!mReadOnly)
-        return INVALID_OPERATION;
-    assert(mZipFp != NULL);
-
-    int fd;
-    fd = dup(fileno(mZipFp));
-    if (fd < 0) {
-        ALOGD("didn't work, errno=%d\n", errno);
-    }
-
-    return fd;
-}
-#endif
-
-
-#if 0
-/*
- * Expand data.
- */
-bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
-{
-    return false;
-}
-#endif
-
-// free the memory when you're done
-void* ZipFile::uncompress(const ZipEntry* entry)
-{
-    size_t unlen = entry->getUncompressedLen();
-    size_t clen = entry->getCompressedLen();
-
-    void* buf = malloc(unlen);
-    if (buf == NULL) {
-        return NULL;
-    }
-
-    fseek(mZipFp, 0, SEEK_SET);
-
-    off_t offset = entry->getFileOffset();
-    if (fseek(mZipFp, offset, SEEK_SET) != 0) {
-        goto bail;
-    }
-
-    switch (entry->getCompressionMethod())
-    {
-        case ZipEntry::kCompressStored: {
-            ssize_t amt = fread(buf, 1, unlen, mZipFp);
-            if (amt != (ssize_t)unlen) {
-                goto bail;
-            }
-#if 0
-            printf("data...\n");
-            const unsigned char* p = (unsigned char*)buf;
-            const unsigned char* end = p+unlen;
-            for (int i=0; i<32 && p < end; i++) {
-                printf("0x%08x ", (int)(offset+(i*0x10)));
-                for (int j=0; j<0x10 && p < end; j++) {
-                    printf(" %02x", *p);
-                    p++;
-                }
-                printf("\n");
-            }
-#endif
-
-            }
-            break;
-        case ZipEntry::kCompressDeflated: {
-            if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
-                goto bail;
-            }
-            }
-            break;
-        default:
-            goto bail;
-    }
-    return buf;
-
-bail:
-    free(buf);
-    return NULL;
-}
-
-
-/*
- * ===========================================================================
- *      ZipFile::EndOfCentralDir
- * ===========================================================================
- */
-
-/*
- * Read the end-of-central-dir fields.
- *
- * "buf" should be positioned at the EOCD signature, and should contain
- * the entire EOCD area including the comment.
- */
-status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
-{
-    /* don't allow re-use */
-    assert(mComment == NULL);
-
-    if (len < kEOCDLen) {
-        /* looks like ZIP file got truncated */
-        ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
-            kEOCDLen, len);
-        return INVALID_OPERATION;
-    }
-
-    /* this should probably be an assert() */
-    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
-        return UNKNOWN_ERROR;
-
-    mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
-    mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
-    mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
-    mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
-    mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
-    mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
-    mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
-
-    // TODO: validate mCentralDirOffset
-
-    if (mCommentLen > 0) {
-        if (kEOCDLen + mCommentLen > len) {
-            ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
-                kEOCDLen, mCommentLen, len);
-            return UNKNOWN_ERROR;
-        }
-        mComment = new unsigned char[mCommentLen];
-        memcpy(mComment, buf + kEOCDLen, mCommentLen);
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Write an end-of-central-directory section.
- */
-status_t ZipFile::EndOfCentralDir::write(FILE* fp)
-{
-    unsigned char buf[kEOCDLen];
-
-    ZipEntry::putLongLE(&buf[0x00], kSignature);
-    ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
-    ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
-    ZipEntry::putShortLE(&buf[0x08], mNumEntries);
-    ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
-    ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
-    ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
-    ZipEntry::putShortLE(&buf[0x14], mCommentLen);
-
-    if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
-        return UNKNOWN_ERROR;
-    if (mCommentLen > 0) {
-        assert(mComment != NULL);
-        if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
-            return UNKNOWN_ERROR;
-    }
-
-    return NO_ERROR;
-}
-
-/*
- * Dump the contents of an EndOfCentralDir object.
- */
-void ZipFile::EndOfCentralDir::dump(void) const
-{
-    ALOGD(" EndOfCentralDir contents:\n");
-    ALOGD("  diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
-        mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
-    ALOGD("  centDirSize=%lu centDirOff=%lu commentLen=%u\n",
-        mCentralDirSize, mCentralDirOffset, mCommentLen);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h
deleted file mode 100644
index 9de92dd..0000000
--- a/tools/aapt2/ZipFile.h
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-//
-// General-purpose Zip archive access.  This class allows both reading and
-// writing to Zip archives, including deletion of existing entries.
-//
-#ifndef __LIBS_ZIPFILE_H
-#define __LIBS_ZIPFILE_H
-
-#include "BigBuffer.h"
-#include "ZipEntry.h"
-
-#include <stdio.h>
-#include <utils/Errors.h>
-#include <vector>
-
-namespace aapt {
-
-using android::status_t;
-
-/*
- * Manipulate a Zip archive.
- *
- * Some changes will not be visible in the until until "flush" is called.
- *
- * The correct way to update a file archive is to make all changes to a
- * copy of the archive in a temporary file, and then unlink/rename over
- * the original after everything completes.  Because we're only interested
- * in using this for packaging, we don't worry about such things.  Crashing
- * after making changes and before flush() completes could leave us with
- * an unusable Zip archive.
- */
-class ZipFile {
-public:
-    ZipFile(void)
-      : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
-      {}
-    ~ZipFile(void) {
-        if (!mReadOnly)
-            flush();
-        if (mZipFp != NULL)
-            fclose(mZipFp);
-        discardEntries();
-    }
-
-    /*
-     * Open a new or existing archive.
-     */
-    enum {
-        kOpenReadOnly   = 0x01,
-        kOpenReadWrite  = 0x02,
-        kOpenCreate     = 0x04,     // create if it doesn't exist
-        kOpenTruncate   = 0x08,     // if it exists, empty it
-    };
-    status_t open(const char* zipFileName, int flags);
-
-    /*
-     * Add a file to the end of the archive.  Specify whether you want the
-     * library to try to store it compressed.
-     *
-     * If "storageName" is specified, the archive will use that instead
-     * of "fileName".
-     *
-     * If there is already an entry with the same name, the call fails.
-     * Existing entries with the same name must be removed first.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t add(const char* fileName, int compressionMethod,
-        ZipEntry** ppEntry)
-    {
-        return add(fileName, fileName, compressionMethod, ppEntry);
-    }
-    status_t add(const char* fileName, const char* storageName,
-        int compressionMethod, ZipEntry** ppEntry)
-    {
-        return addCommon(fileName, NULL, 0, storageName,
-                         ZipEntry::kCompressStored,
-                         compressionMethod, ppEntry);
-    }
-
-    /*
-     * Add a file that is already compressed with gzip.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t addGzip(const char* fileName, const char* storageName,
-        ZipEntry** ppEntry)
-    {
-        return addCommon(fileName, NULL, 0, storageName,
-                         ZipEntry::kCompressDeflated,
-                         ZipEntry::kCompressDeflated, ppEntry);
-    }
-
-    /*
-     * Add a file from an in-memory data buffer.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t add(const void* data, size_t size, const char* storageName,
-        int compressionMethod, ZipEntry** ppEntry)
-    {
-        return addCommon(NULL, data, size, storageName,
-                         ZipEntry::kCompressStored,
-                         compressionMethod, ppEntry);
-    }
-
-    status_t add(const BigBuffer& data, const char* storageName,
-        int compressionMethod, ZipEntry** ppEntry);
-
-    /*
-     * Add an entry by copying it from another zip file.  If storageName is
-     * non-NULL, the entry will be inserted with the name storageName, otherwise
-     * it will have the same name as the source entry.  If "padding" is
-     * nonzero, the specified number of bytes will be added to the "extra"
-     * field in the header.
-     *
-     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
-     */
-    status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
-                 const char* storageName, int padding, ZipEntry** ppEntry);
-
-    /*
-     * Mark an entry as having been removed.  It is not actually deleted
-     * from the archive or our internal data structures until flush() is
-     * called.
-     */
-    status_t remove(ZipEntry* pEntry);
-
-    /*
-     * Flush changes.  If mNeedCDRewrite is set, this writes the central dir.
-     */
-    status_t flush(void);
-
-    /*
-     * Expand the data into the buffer provided.  The buffer must hold
-     * at least <uncompressed len> bytes.  Variation expands directly
-     * to a file.
-     *
-     * Returns "false" if an error was encountered in the compressed data.
-     */
-    //bool uncompress(const ZipEntry* pEntry, void* buf) const;
-    //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
-    void* uncompress(const ZipEntry* pEntry);
-
-    /*
-     * Get an entry, by name.  Returns NULL if not found.
-     *
-     * Does not return entries pending deletion.
-     */
-    ZipEntry* getEntryByName(const char* fileName) const;
-
-    /*
-     * Get the Nth entry in the archive.
-     *
-     * This will return an entry that is pending deletion.
-     */
-    int getNumEntries(void) const { return mEntries.size(); }
-    ZipEntry* getEntryByIndex(int idx) const;
-
-private:
-    /* these are private and not defined */
-    ZipFile(const ZipFile& src);
-    ZipFile& operator=(const ZipFile& src);
-
-    class EndOfCentralDir {
-    public:
-        EndOfCentralDir(void) :
-            mDiskNumber(0),
-            mDiskWithCentralDir(0),
-            mNumEntries(0),
-            mTotalNumEntries(0),
-            mCentralDirSize(0),
-            mCentralDirOffset(0),
-            mCommentLen(0),
-            mComment(NULL)
-            {}
-        virtual ~EndOfCentralDir(void) {
-            delete[] mComment;
-        }
-
-        status_t readBuf(const unsigned char* buf, int len);
-        status_t write(FILE* fp);
-
-        //unsigned long   mSignature;
-        unsigned short  mDiskNumber;
-        unsigned short  mDiskWithCentralDir;
-        unsigned short  mNumEntries;
-        unsigned short  mTotalNumEntries;
-        unsigned long   mCentralDirSize;
-        unsigned long   mCentralDirOffset;      // offset from first disk
-        unsigned short  mCommentLen;
-        unsigned char*  mComment;
-
-        enum {
-            kSignature      = 0x06054b50,
-            kEOCDLen        = 22,       // EndOfCentralDir len, excl. comment
-
-            kMaxCommentLen  = 65535,    // longest possible in ushort
-            kMaxEOCDSearch  = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
-
-        };
-
-        void dump(void) const;
-    };
-
-
-    /* read all entries in the central dir */
-    status_t readCentralDir(void);
-
-    /* crunch deleted entries out */
-    status_t crunchArchive(void);
-
-    /* clean up mEntries */
-    void discardEntries(void);
-
-    /* common handler for all "add" functions */
-    status_t addCommon(const char* fileName, const void* data, size_t size,
-        const char* storageName, int sourceType, int compressionMethod,
-        ZipEntry** ppEntry);
-
-    /* copy all of "srcFp" into "dstFp" */
-    status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
-    /* copy all of "data" into "dstFp" */
-    status_t copyDataToFp(FILE* dstFp,
-        const void* data, size_t size, unsigned long* pCRC32);
-    /* copy some of "srcFp" into "dstFp" */
-    status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
-        unsigned long* pCRC32);
-    /* like memmove(), but on parts of a single file */
-    status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
-    /* compress all of "srcFp" into "dstFp", using Deflate */
-    status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
-        const void* data, size_t size, unsigned long* pCRC32);
-
-    /* get modification date from a file descriptor */
-    time_t getModTime(int fd);
-
-    /*
-     * We use stdio FILE*, which gives us buffering but makes dealing
-     * with files >2GB awkward.  Until we support Zip64, we're fine.
-     */
-    FILE*           mZipFp;             // Zip file pointer
-
-    /* one of these per file */
-    EndOfCentralDir mEOCD;
-
-    /* did we open this read-only? */
-    bool            mReadOnly;
-
-    /* set this when we trash the central dir */
-    bool            mNeedCDRewrite;
-
-    /*
-     * One ZipEntry per entry in the zip file.  I'm using pointers instead
-     * of objects because it's easier than making operator= work for the
-     * classes and sub-classes.
-     */
-    std::vector<ZipEntry*>   mEntries;
-};
-
-}; // namespace aapt
-
-#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
new file mode 100644
index 0000000..0bc5dce
--- /dev/null
+++ b/tools/aapt2/compile/Compile.cpp
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "Diagnostics.h"
+#include "Flags.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "XmlDom.h"
+#include "XmlPullParser.h"
+
+#include "compile/IdAssigner.h"
+#include "compile/Png.h"
+#include "compile/XmlIdCollector.h"
+#include "flatten/FileExportWriter.h"
+#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
+#include "util/Files.h"
+#include "util/Maybe.h"
+#include "util/Util.h"
+
+#include <fstream>
+#include <string>
+
+namespace aapt {
+
+struct ResourcePathData {
+    Source source;
+    std::u16string resourceDir;
+    std::u16string name;
+    std::string extension;
+
+    // Original config str. We keep this because when we parse the config, we may add on
+    // version qualifiers. We want to preserve the original input so the output is easily
+    // computed before hand.
+    std::string configStr;
+    ConfigDescription config;
+};
+
+/**
+ * Resource file paths are expected to look like:
+ * [--/res/]type[-config]/name
+ */
+static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
+                                                       std::string* outError) {
+    std::vector<std::string> parts = util::split(path, file::sDirSep);
+    if (parts.size() < 2) {
+        if (outError) *outError = "bad resource path";
+        return {};
+    }
+
+    std::string& dir = parts[parts.size() - 2];
+    StringPiece dirStr = dir;
+
+    StringPiece configStr;
+    ConfigDescription config;
+    size_t dashPos = dir.find('-');
+    if (dashPos != std::string::npos) {
+        configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+        if (!ConfigDescription::parse(configStr, &config)) {
+            if (outError) {
+                std::stringstream errStr;
+                errStr << "invalid configuration '" << configStr << "'";
+                *outError = errStr.str();
+            }
+            return {};
+        }
+        dirStr = dirStr.substr(0, dashPos);
+    }
+
+    std::string& filename = parts[parts.size() - 1];
+    StringPiece name = filename;
+    StringPiece extension;
+    size_t dotPos = filename.find('.');
+    if (dotPos != std::string::npos) {
+        extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+        name = name.substr(0, dotPos);
+    }
+
+    return ResourcePathData{
+            Source{ path },
+            util::utf8ToUtf16(dirStr),
+            util::utf8ToUtf16(name),
+            extension.toString(),
+            configStr.toString(),
+            config
+    };
+}
+
+struct CompileOptions {
+    std::string outputPath;
+    Maybe<std::u16string> product;
+    bool verbose = false;
+};
+
+static std::string buildIntermediateFilename(const std::string outDir,
+                                             const ResourcePathData& data) {
+    std::stringstream name;
+    name << data.resourceDir;
+    if (!data.configStr.empty()) {
+        name << "-" << data.configStr;
+    }
+    name << "_" << data.name << "." << data.extension << ".flat";
+    std::string outPath = outDir;
+    file::appendPath(&outPath, name.str());
+    return outPath;
+}
+
+static bool compileTable(IAaptContext* context, const CompileOptions& options,
+                         const ResourcePathData& pathData, const std::string& outputPath) {
+    ResourceTable table;
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+
+        // Parse the values file from XML.
+        XmlPullParser xmlParser(fin);
+        ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
+                                 pathData.config, ResourceParserOptions{ options.product });
+        if (!resParser.parse(&xmlParser)) {
+            return false;
+        }
+
+        fin.close();
+    }
+
+    ResourceTablePackage* pkg = table.createPackage(context->getCompilationPackage());
+    if (!pkg->id) {
+        // If no package ID was set while parsing (public identifiers), auto assign an ID.
+        pkg->id = context->getPackageId();
+    }
+
+    // Assign IDs to prepare the table for flattening.
+    IdAssigner idAssigner;
+    if (!idAssigner.consume(context, &table)) {
+        return false;
+    }
+
+    // Flatten the table.
+    BigBuffer buffer(1024);
+    TableFlattenerOptions tableFlattenerOptions;
+    tableFlattenerOptions.useExtendedChunks = true;
+    TableFlattener flattener(&buffer, tableFlattenerOptions);
+    if (!flattener.consume(context, &table)) {
+        return false;
+    }
+
+    // Build the output filename.
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Write it to disk.
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compileXml(IAaptContext* context, const CompileOptions& options,
+                       const ResourcePathData& pathData, const std::string& outputPath) {
+
+    std::unique_ptr<XmlResource> xmlRes;
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+        xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
+
+        fin.close();
+    }
+
+    if (!xmlRes) {
+        return false;
+    }
+
+    // Collect IDs that are defined here.
+    XmlIdCollector collector;
+    if (!collector.consume(context, xmlRes.get())) {
+        return false;
+    }
+
+    xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    xmlRes->file.config = pathData.config;
+    xmlRes->file.source = pathData.source;
+
+    BigBuffer buffer(1024);
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
+
+    XmlFlattenerOptions xmlFlattenerOptions;
+    xmlFlattenerOptions.keepRawValues = true;
+    XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
+    if (!flattener.consume(context, xmlRes.get())) {
+        return false;
+    }
+
+    fileExportWriter.finish();
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Write it to disk.
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compilePng(IAaptContext* context, const CompileOptions& options,
+                       const ResourcePathData& pathData, const std::string& outputPath) {
+    BigBuffer buffer(4096);
+    ResourceFile resFile;
+    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.config = pathData.config;
+    resFile.source = pathData.source;
+
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+        Png png(context->getDiagnostics());
+        if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
+            return false;
+        }
+    }
+
+    fileExportWriter.finish();
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compileFile(IAaptContext* context, const CompileOptions& options,
+                        const ResourcePathData& pathData, const std::string& outputPath) {
+    BigBuffer buffer(256);
+    ResourceFile resFile;
+    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.config = pathData.config;
+    resFile.source = pathData.source;
+
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
+
+    std::string errorStr;
+    Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
+    if (!f) {
+        context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
+        return false;
+    }
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Manually set the size and don't call finish(). This is because we are not copying from
+    // the buffer the entire file.
+    fileExportWriter.getChunkHeader()->size =
+            util::hostToDevice32(buffer.size() + f.value().getDataLength());
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+class CompileContext : public IAaptContext {
+private:
+    StdErrDiagnostics mDiagnostics;
+
+public:
+    IDiagnostics* getDiagnostics() override {
+       return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+       abort();
+       return nullptr;
+    }
+
+    StringPiece16 getCompilationPackage() override {
+       return {};
+    }
+
+    uint8_t getPackageId() override {
+       return 0x0;
+    }
+
+    ISymbolTable* getExternalSymbols() override {
+       abort();
+       return nullptr;
+    }
+};
+
+/**
+ * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
+ */
+int compile(const std::vector<StringPiece>& args) {
+    CompileOptions options;
+
+    Maybe<std::string> product;
+    Flags flags = Flags()
+            .requiredFlag("-o", "Output path", &options.outputPath)
+            .optionalFlag("--product", "Product type to compile", &product)
+            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+    if (!flags.parse("aapt2 compile", args, &std::cerr)) {
+        return 1;
+    }
+
+    if (product) {
+        options.product = util::utf8ToUtf16(product.value());
+    }
+
+    CompileContext context;
+
+    std::vector<ResourcePathData> inputData;
+    inputData.reserve(flags.getArgs().size());
+
+    // Collect data from the path for each input file.
+    for (const std::string& arg : flags.getArgs()) {
+        std::string errorStr;
+        if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+            inputData.push_back(std::move(pathData.value()));
+        } else {
+            context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+            return 1;
+        }
+    }
+
+    bool error = false;
+    for (ResourcePathData& pathData : inputData) {
+        if (options.verbose) {
+            context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
+        }
+
+        if (pathData.resourceDir == u"values") {
+            // Overwrite the extension.
+            pathData.extension = "arsc";
+
+            const std::string outputFilename = buildIntermediateFilename(
+                    options.outputPath, pathData);
+            if (!compileTable(&context, options, pathData, outputFilename)) {
+                error = true;
+            }
+
+        } else {
+            const std::string outputFilename = buildIntermediateFilename(options.outputPath,
+                                                                         pathData);
+            if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
+                if (*type != ResourceType::kRaw) {
+                    if (pathData.extension == "xml") {
+                        if (!compileXml(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    } else if (pathData.extension == "png" || pathData.extension == "9.png") {
+                        if (!compilePng(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    } else {
+                        if (!compileFile(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    }
+                } else {
+                    if (!compileFile(&context, options, pathData, outputFilename)) {
+                        error = true;
+                    }
+                }
+            } else {
+                context.getDiagnostics()->error(
+                        DiagMessage() << "invalid file path '" << pathData.source << "'");
+                error = true;
+            }
+        }
+    }
+
+    if (error) {
+        return 1;
+    }
+    return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
new file mode 100644
index 0000000..80c6bbc
--- /dev/null
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+
+#include "compile/IdAssigner.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
+#include <bitset>
+#include <cassert>
+#include <set>
+
+namespace aapt {
+
+bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
+    std::bitset<256> usedTypeIds;
+    std::set<uint16_t> usedEntryIds;
+
+    for (auto& package : table->packages) {
+        assert(package->id && "packages must have manually assigned IDs");
+
+        usedTypeIds.reset();
+
+        // Type ID 0 is invalid, reserve it.
+        usedTypeIds.set(0);
+
+        // Collect used type IDs.
+        for (auto& type : package->types) {
+            if (type->id) {
+                usedEntryIds.clear();
+
+                if (usedTypeIds[type->id.value()]) {
+                    // This ID is already taken!
+                    context->getDiagnostics()->error(DiagMessage()
+                                                     << "type '" << type->type << "' in "
+                                                     << "package '" << package->name << "' has "
+                                                     << "duplicate ID "
+                                                     << std::hex << (int) type->id.value()
+                                                     << std::dec);
+                    return false;
+                }
+
+                // Mark the type ID as taken.
+                usedTypeIds.set(type->id.value());
+            }
+
+            // Collect used entry IDs.
+            for (auto& entry : type->entries) {
+                if (entry->id) {
+                    // Mark entry ID as taken.
+                    if (!usedEntryIds.insert(entry->id.value()).second) {
+                        // This ID existed before!
+                        ResourceNameRef nameRef =
+                                { package->name, type->type, entry->name };
+                        ResourceId takenId(package->id.value(), type->id.value(),
+                                           entry->id.value());
+                        context->getDiagnostics()->error(DiagMessage()
+                                                         << "resource '" << nameRef << "' "
+                                                         << "has duplicate ID '"
+                                                         << takenId << "'");
+                        return false;
+                    }
+                }
+            }
+
+            // Assign unused entry IDs.
+            const auto endUsedEntryIter = usedEntryIds.end();
+            auto nextUsedEntryIter = usedEntryIds.begin();
+            uint16_t nextId = 0;
+            for (auto& entry : type->entries) {
+                if (!entry->id) {
+                    // Assign the next available entryID.
+                    while (nextUsedEntryIter != endUsedEntryIter &&
+                            nextId == *nextUsedEntryIter) {
+                        nextId++;
+                        ++nextUsedEntryIter;
+                    }
+                    entry->id = nextId++;
+                }
+            }
+        }
+
+        // Assign unused type IDs.
+        size_t nextTypeId = 0;
+        for (auto& type : package->types) {
+            if (!type->id) {
+                while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) {
+                    nextTypeId++;
+                }
+                type->id = static_cast<uint8_t>(nextTypeId);
+                nextTypeId++;
+            }
+        }
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/compile/IdAssigner.h
similarity index 61%
copy from tools/aapt2/Compat_test.cpp
copy to tools/aapt2/compile/IdAssigner.h
index 96aee44..514df3a 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -14,20 +14,21 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#ifndef AAPT_COMPILE_IDASSIGNER_H
+#define AAPT_COMPILE_IDASSIGNER_H
+
+#include "process/IResourceTableConsumer.h"
 
 namespace aapt {
 
-TEST(CompatTest, VersionAttributesInStyle) {
-}
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
+/**
+ * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
+ * in between fixed ID assignments.
+ */
+struct IdAssigner : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
 
 } // namespace aapt
+
+#endif /* AAPT_COMPILE_IDASSIGNER_H */
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
new file mode 100644
index 0000000..e25a17a
--- /dev/null
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compile/IdAssigner.h"
+
+#include "test/Context.h"
+#include "test/Builders.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+::testing::AssertionResult verifyIds(ResourceTable* table);
+
+TEST(IdAssignerTest, AssignIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/foo")
+            .addSimple(u"@android:attr/bar")
+            .addSimple(u"@android:id/foo")
+            .setPackageId(u"android", 0x01)
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    IdAssigner assigner;
+
+    ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+    ASSERT_TRUE(verifyIds(table.get()));
+}
+
+TEST(IdAssignerTest, AssignIdsWithReservedIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/foo", ResourceId(0x01040006))
+            .addSimple(u"@android:attr/bar")
+            .addSimple(u"@android:id/foo")
+            .addSimple(u"@app:id/biz")
+            .setPackageId(u"android", 0x01)
+            .setPackageId(u"app", 0x7f)
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    IdAssigner assigner;
+
+    ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+    ASSERT_TRUE(verifyIds(table.get()));
+}
+
+TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/foo", ResourceId(0x01040006))
+            .addSimple(u"@android:attr/bar", ResourceId(0x01040006))
+            .setPackageId(u"android", 0x01)
+            .setPackageId(u"app", 0x7f)
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    IdAssigner assigner;
+
+    ASSERT_FALSE(assigner.consume(context.get(), table.get()));
+}
+
+::testing::AssertionResult verifyIds(ResourceTable* table) {
+    std::set<uint8_t> packageIds;
+    for (auto& package : table->packages) {
+        if (!package->id) {
+            return ::testing::AssertionFailure() << "package " << package->name << " has no ID";
+        }
+
+        if (!packageIds.insert(package->id.value()).second) {
+            return ::testing::AssertionFailure() << "package " << package->name
+                    << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec;
+        }
+    }
+
+    for (auto& package : table->packages) {
+        std::set<uint8_t> typeIds;
+        for (auto& type : package->types) {
+            if (!type->id) {
+                return ::testing::AssertionFailure() << "type " << type->type << " of package "
+                        << package->name << " has no ID";
+            }
+
+            if (!typeIds.insert(type->id.value()).second) {
+                return ::testing::AssertionFailure() << "type " << type->type
+                        << " of package " << package->name << " has non-unique ID "
+                        << std::hex << (int) type->id.value() << std::dec;
+            }
+        }
+
+
+        for (auto& type : package->types) {
+            std::set<uint16_t> entryIds;
+            for (auto& entry : type->entries) {
+                if (!entry->id) {
+                    return ::testing::AssertionFailure() << "entry " << entry->name << " of type "
+                            << type->type << " of package " << package->name << " has no ID";
+                }
+
+                if (!entryIds.insert(entry->id.value()).second) {
+                    return ::testing::AssertionFailure() << "entry " << entry->name
+                            << " of type " << type->type << " of package " << package->name
+                            << " has non-unique ID "
+                            << std::hex << (int) entry->id.value() << std::dec;
+                }
+            }
+        }
+    }
+    return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/compile/Png.cpp
similarity index 92%
rename from tools/aapt2/Png.cpp
rename to tools/aapt2/compile/Png.cpp
index 4e9b68e..9837c4e 100644
--- a/tools/aapt2/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
-#include "Logger.h"
+#include "util/BigBuffer.h"
 #include "Png.h"
 #include "Source.h"
-#include "Util.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <iostream>
@@ -95,15 +94,14 @@
 }
 
 static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
-    SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
-    logger->warn() << warningMessage << "." << std::endl;
+    IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+    diag->warn(DiagMessage() << warningMessage);
 }
 
 
-static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
-                    std::string* outError) {
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
     if (setjmp(png_jmpbuf(readPtr))) {
-        *outError = "failed reading png";
+        diag->error(DiagMessage() << "failed reading png");
         return false;
     }
 
@@ -229,7 +227,7 @@
 #define MAX(a,b) ((a)>(b)?(a):(b))
 #define ABS(a)   ((a)<0?-(a):(a))
 
-static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
                           png_colorp rgbPalette, png_bytep alphaPalette,
                           int *paletteEntries, bool *hasTransparency, int *colorType,
                           png_bytepp outRows) {
@@ -363,9 +361,9 @@
         *colorType = PNG_COLOR_TYPE_PALETTE;
     } else {
         if (maxGrayDeviation <= grayscaleTolerance) {
-            logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
-                           << ")."
-                           << std::endl;
+            diag->note(DiagMessage()
+                       << "forcing image to gray (max deviation = "
+                       << maxGrayDeviation << ")");
             *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
         } else {
             *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
@@ -411,10 +409,10 @@
     }
 }
 
-static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
-                     int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
+                     int grayScaleTolerance) {
     if (setjmp(png_jmpbuf(writePtr))) {
-        *outError = "failed to write png";
+        diag->error(DiagMessage() << "failed to write png");
         return false;
     }
 
@@ -442,9 +440,9 @@
     png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
 
     if (kDebug) {
-        logger->note() << "writing image: w = " << info->width
-                       << ", h = " << info->height
-                       << std::endl;
+        diag->note(DiagMessage()
+                   << "writing image: w = " << info->width
+                   << ", h = " << info->height);
     }
 
     png_color rgbPalette[256];
@@ -452,7 +450,7 @@
     bool hasTransparency;
     int paletteEntries;
 
-    analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+    analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
                   &paletteEntries, &hasTransparency, &colorType, outRows);
 
     // If the image is a 9-patch, we need to preserve it as a ARGB file to make
@@ -465,22 +463,22 @@
     if (kDebug) {
         switch (colorType) {
         case PNG_COLOR_TYPE_PALETTE:
-            logger->note() << "has " << paletteEntries
-                           << " colors" << (hasTransparency ? " (with alpha)" : "")
-                           << ", using PNG_COLOR_TYPE_PALLETTE."
-                           << std::endl;
+            diag->note(DiagMessage()
+                       << "has " << paletteEntries
+                       << " colors" << (hasTransparency ? " (with alpha)" : "")
+                       << ", using PNG_COLOR_TYPE_PALLETTE");
             break;
         case PNG_COLOR_TYPE_GRAY:
-            logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+            diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
             break;
         case PNG_COLOR_TYPE_GRAY_ALPHA:
-            logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+            diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
             break;
         case PNG_COLOR_TYPE_RGB:
-            logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+            diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
             break;
         case PNG_COLOR_TYPE_RGB_ALPHA:
-            logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+            diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
             break;
         }
     }
@@ -511,7 +509,7 @@
 
         // base 9 patch data
         if (kDebug) {
-            logger->note() << "adding 9-patch info..." << std::endl;
+            diag->note(DiagMessage() << "adding 9-patch info..");
         }
         strcpy((char*)unknowns[pIndex].name, "npTc");
         unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
@@ -587,10 +585,10 @@
                  &compressionType, nullptr);
 
     if (kDebug) {
-        logger->note() << "image written: w = " << width << ", h = " << height
-                       << ", d = " << bitDepth << ", colors = " << colorType
-                       << ", inter = " << interlaceType << ", comp = " << compressionType
-                       << std::endl;
+        diag->note(DiagMessage()
+                   << "image written: w = " << width << ", h = " << height
+                   << ", d = " << bitDepth << ", colors = " << colorType
+                   << ", inter = " << interlaceType << ", comp = " << compressionType);
     }
     return true;
 }
@@ -1192,23 +1190,22 @@
 }
 
 
-bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer,
-                  const Options& options, std::string* outError) {
+bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+                  const PngOptions& options) {
     png_byte signature[kPngSignatureSize];
 
     // Read the PNG signature first.
-    if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
-        *outError = strerror(errno);
+    if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+        mDiag->error(DiagMessage() << strerror(errno));
         return false;
     }
 
     // If the PNG signature doesn't match, bail early.
     if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
-        *outError = "not a valid png file";
+        mDiag->error(DiagMessage() << "not a valid png file");
         return false;
     }
 
-    SourceLogger logger(source);
     bool result = false;
     png_structp readPtr = nullptr;
     png_infop infoPtr = nullptr;
@@ -1218,40 +1215,42 @@
 
     readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
     if (!readPtr) {
-        *outError = "failed to allocate read ptr";
+        mDiag->error(DiagMessage() << "failed to allocate read ptr");
         goto bail;
     }
 
     infoPtr = png_create_info_struct(readPtr);
     if (!infoPtr) {
-        *outError = "failed to allocate info ptr";
+        mDiag->error(DiagMessage() << "failed to allocate info ptr");
         goto bail;
     }
 
-    png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+    png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
 
     // Set the read function to read from std::istream.
-    png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+    png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
 
-    if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+    if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
         goto bail;
     }
 
     if (util::stringEndsWith<char>(source.path, ".9.png")) {
-        if (!do9Patch(&pngInfo, outError)) {
+        std::string errorMsg;
+        if (!do9Patch(&pngInfo, &errorMsg)) {
+            mDiag->error(DiagMessage() << errorMsg);
             goto bail;
         }
     }
 
     writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
     if (!writePtr) {
-        *outError = "failed to allocate write ptr";
+        mDiag->error(DiagMessage() << "failed to allocate write ptr");
         goto bail;
     }
 
     writeInfoPtr = png_create_info_struct(writePtr);
     if (!writeInfoPtr) {
-        *outError = "failed to allocate write info ptr";
+        mDiag->error(DiagMessage() << "failed to allocate write info ptr");
         goto bail;
     }
 
@@ -1260,8 +1259,7 @@
     // Set the write function to write to std::ostream.
     png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
 
-    if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
-                  outError)) {
+    if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
         goto bail;
     }
 
diff --git a/tools/aapt2/Png.h b/tools/aapt2/compile/Png.h
similarity index 71%
rename from tools/aapt2/Png.h
rename to tools/aapt2/compile/Png.h
index 4577ab8..345ff6c 100644
--- a/tools/aapt2/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -17,7 +17,8 @@
 #ifndef AAPT_PNG_H
 #define AAPT_PNG_H
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
+#include "Diagnostics.h"
 #include "Source.h"
 
 #include <iostream>
@@ -25,13 +26,20 @@
 
 namespace aapt {
 
-struct Png {
-    struct Options {
-        int grayScaleTolerance = 0;
-    };
+struct PngOptions {
+    int grayScaleTolerance = 0;
+};
 
-    bool process(const Source& source, std::istream& input, BigBuffer* outBuffer,
-                 const Options& options, std::string* outError);
+class Png {
+public:
+    Png(IDiagnostics* diag) : mDiag(diag) {
+    }
+
+    bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+                 const PngOptions& options);
+
+private:
+    IDiagnostics* mDiag;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
new file mode 100644
index 0000000..dfdf710
--- /dev/null
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "XmlDom.h"
+
+#include "compile/XmlIdCollector.h"
+
+#include <algorithm>
+#include <vector>
+
+namespace aapt {
+
+namespace {
+
+static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
+    return a.name < b;
+}
+
+struct IdCollector : public xml::Visitor {
+    using xml::Visitor::visit;
+
+    std::vector<SourcedResourceName>* mOutSymbols;
+
+    IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) {
+    }
+
+    void visit(xml::Element* element) override {
+        for (xml::Attribute& attr : element->attributes) {
+            ResourceNameRef name;
+            bool create = false;
+            if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) {
+                if (create && name.type == ResourceType::kId) {
+                    auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
+                                                 name, cmpName);
+                    if (iter == mOutSymbols->end() || iter->name != name) {
+                        mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(),
+                                                                       element->lineNumber });
+                    }
+                }
+            }
+        }
+
+        xml::Visitor::visit(element);
+    }
+};
+
+} // namespace
+
+bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) {
+    xmlRes->file.exportedSymbols.clear();
+    IdCollector collector(&xmlRes->file.exportedSymbols);
+    xmlRes->root->accept(&collector);
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/compile/XmlIdCollector.h
similarity index 70%
rename from tools/aapt2/Compat_test.cpp
rename to tools/aapt2/compile/XmlIdCollector.h
index 96aee44..96a58f2 100644
--- a/tools/aapt2/Compat_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -14,20 +14,17 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#ifndef AAPT_XMLIDCOLLECTOR_H
+#define AAPT_XMLIDCOLLECTOR_H
+
+#include "process/IResourceTableConsumer.h"
 
 namespace aapt {
 
-TEST(CompatTest, VersionAttributesInStyle) {
-}
-
-TEST(CompatTest, VersionAttributesInXML) {
-}
-
-TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
-}
-
-TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
-}
+struct XmlIdCollector : public IXmlResourceConsumer {
+    bool consume(IAaptContext* context, XmlResource* xmlRes) override;
+};
 
 } // namespace aapt
+
+#endif /* AAPT_XMLIDCOLLECTOR_H */
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
new file mode 100644
index 0000000..c703f451
--- /dev/null
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compile/XmlIdCollector.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <algorithm>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlIdCollectorTest, CollectsIds) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:id="@+id/foo"
+                  text="@+id/bar">
+              <SubView android:id="@+id/car"
+                       class="@+id/bar"/>
+            </View>)EOF");
+
+    XmlIdCollector collector;
+    ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+
+    EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+                             SourcedResourceName{ test::parseNameOrDie(u"@id/foo"), 3u }));
+
+    EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+                             SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u }));
+
+    EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+                             SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u }));
+}
+
+TEST(XmlIdCollectorTest, DontCollectNonIds) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+
+    XmlIdCollector collector;
+    ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+
+    EXPECT_TRUE(doc->file.exportedSymbols.empty());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml
index 8533c28..d3b2fbe 100644
--- a/tools/aapt2/data/AndroidManifest.xml
+++ b/tools/aapt2/data/AndroidManifest.xml
@@ -2,6 +2,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.app">
     <application
-        android:name=".Activity">
+        android:name=".ActivityMain">
     </application>
 </manifest>
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
index 91ff5fe..37012de 100644
--- a/tools/aapt2/data/Makefile
+++ b/tools/aapt2/data/Makefile
@@ -21,63 +21,41 @@
 # AAPT2 custom rules.
 ##
 
-PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
-PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
+PRIVATE_R_FILE := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
+$(info PRIVATE_R_FILE = $(PRIVATE_R_FILE))
 
 # Eg: framework.apk, etc.
 PRIVATE_INCLUDES := $(FRAMEWORK)
 $(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES))
 
-# Eg: gen/com/android/app/R.java
-PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
-$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
-
 # Eg: res/drawable/icon.png, res/values/styles.xml
 PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
 $(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
 
-# Eg: drawable, values, layouts
-PRIVATE_RESOURCE_TYPES := \
-	$(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
-$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
+PRIVATE_RESOURCE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))))
+PRIVATE_RESOURCE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(PRIVATE_RESOURCE_OBJECTS:.xml=.arsc.flat))
+$(info PRIVATE_RESOURCE_OBJECTS = $(PRIVATE_RESOURCE_OBJECTS))
 
-# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
-PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
-$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
+PRIVATE_FILE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))))
+PRIVATE_FILE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(addsuffix .flat,$(PRIVATE_FILE_OBJECTS)))
+$(info PRIVATE_FILE_OBJECTS = $(PRIVATE_FILE_OBJECTS))
 
-# Generates rules for collect phase.
-# $1: Resource type (values-v4)
-# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
-define make-collect-rule
-$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
-	$(AAPT) compile -o $$@ $$^
-endef
+.SECONDEXPANSION:
 
-# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
-$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
+$(LOCAL_OUT)/%.arsc.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%).xml
+	$(AAPT) compile -o $(LOCAL_OUT) $<
 
-# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
-$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
-	$(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v
+$(LOCAL_OUT)/%.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%)
+	$(AAPT) compile -o $(LOCAL_OUT) $<
 
-# R.java: gen/com/android/app/R.java <- out/resources.arsc
-# No action since R.java is generated when out/resources.arsc is.
-$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
-
-# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
-$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
-	$(ZIPALIGN) $< $@
+$(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: AndroidManifest.xml
+$(PRIVATE_R_FILE) $(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: $(PRIVATE_FILE_OBJECTS) $(PRIVATE_RESOURCE_OBJECTS)
+	$(AAPT) link -o $(LOCAL_OUT)/package.apk --manifest AndroidManifest.xml --java $(LOCAL_GEN) --proguard $(LOCAL_PROGUARD) -I $(PRIVATE_INCLUDES) $(filter-out AndroidManifest.xml,$^) -v
 
 # Create the out directory if needed.
 dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
 
-.PHONY: java
-java: $(PRIVATE_R_JAVA)
-
-.PHONY: assemble
-assemble: $(PRIVATE_APK_ALIGNED)
-
 .PHONY: all
-all: assemble java
+all: $(LOCAL_OUT)/package.apk $(LOCAL_PROGUARD) $(PRIVATE_R_FILE)
 
 .DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/res/layout-v21/main.xml b/tools/aapt2/data/res/layout-v21/main.xml
new file mode 100644
index 0000000..959b349
--- /dev/null
+++ b/tools/aapt2/data/res/layout-v21/main.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:support="http://schemas.android.com/apk/res/android.appcompat"
+    android:id="@+id/view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+</LinearLayout>
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml
index 50a51d9..8a5e9e8 100644
--- a/tools/aapt2/data/res/layout/main.xml
+++ b/tools/aapt2/data/res/layout/main.xml
@@ -14,8 +14,8 @@
         android:layout_width="1dp"
         android:onClick="doClick"
         android:text="@{user.name}"
+        android:background="#ffffff"
         android:layout_height="match_parent"
-        app:layout_width="@support:bool/allow"
         app:flags="complex|weak"
         android:colorAccent="#ffffff"/>
 </LinearLayout>
diff --git a/tools/aapt2/data/res/raw/test.txt b/tools/aapt2/data/res/raw/test.txt
new file mode 100644
index 0000000..b14df64
--- /dev/null
+++ b/tools/aapt2/data/res/raw/test.txt
@@ -0,0 +1 @@
+Hi
diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml
index d0b19a3..2bbdad1 100644
--- a/tools/aapt2/data/res/values/styles.xml
+++ b/tools/aapt2/data/res/values/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat">
-    <style name="App" parent="android.appcompat:Platform.AppCompat">
+    <style name="App">
         <item name="android:background">@color/primary</item>
         <item name="android:colorPrimary">@color/primary</item>
         <item name="android:colorPrimaryDark">@color/primary_dark</item>
@@ -8,8 +8,8 @@
     </style>
     <attr name="custom" format="reference" />
     <style name="Pop">
-        <item name="custom">@drawable/image</item>
-        <item name="android:focusable">@lib:bool/allow</item>
+        <item name="custom">@android:drawable/btn_default</item>
+        <item name="android:focusable">true</item>
     </style>
     <string name="yo">@string/wow</string>
 
diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml
index d3ead34..d7ab1c8 100644
--- a/tools/aapt2/data/res/values/test.xml
+++ b/tools/aapt2/data/res/values/test.xml
@@ -3,7 +3,7 @@
     <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string>
     <public name="hooha" type="string" id="0x7f020001"/>
     <string name="wow">@android:string/ok</string>
-    <public name="image" type="drawable" id="0x7f060000" />
+    <public name="layout_width" type="attr" />
     <attr name="layout_width" format="boolean" />
     <attr name="flags">
         <flag name="complex" value="1" />
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
new file mode 100644
index 0000000..6db13b8
--- /dev/null
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatten/Archive.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+#include <ziparchive/zip_writer.h>
+
+namespace aapt {
+
+namespace {
+
+struct DirectoryWriter : public IArchiveWriter {
+    std::string mOutDir;
+    std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
+
+    explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) {
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                             const BigBuffer& buffer) override {
+        std::string fullPath = mOutDir;
+        file::appendPath(&fullPath, path);
+        file::mkdirs(file::getStem(fullPath));
+
+        std::ofstream fout(fullPath, std::ofstream::binary);
+        if (!fout) {
+            return nullptr;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size()));
+        return mEntries.back().get();
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
+                             size_t offset, size_t len) override {
+        std::string fullPath = mOutDir;
+        file::appendPath(&fullPath, path);
+        file::mkdirs(file::getStem(fullPath));
+
+        std::ofstream fout(fullPath, std::ofstream::binary);
+        if (!fout) {
+            return nullptr;
+        }
+
+        if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len));
+        return mEntries.back().get();
+    }
+
+    virtual ~DirectoryWriter() {
+
+    }
+};
+
+struct ZipFileWriter : public IArchiveWriter {
+    FILE* mFile;
+    std::unique_ptr<ZipWriter> mWriter;
+    std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
+
+    explicit ZipFileWriter(const StringPiece& path) {
+        mFile = fopen(path.data(), "w+b");
+        if (mFile) {
+            mWriter = util::make_unique<ZipWriter>(mFile);
+        }
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                             const BigBuffer& buffer) override {
+        if (!mWriter) {
+            return nullptr;
+        }
+
+        size_t zipFlags = 0;
+        if (flags & ArchiveEntry::kCompress) {
+            zipFlags |= ZipWriter::kCompress;
+        }
+
+        if (flags & ArchiveEntry::kAlign) {
+            zipFlags |= ZipWriter::kAlign32;
+        }
+
+        int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+        if (result != 0) {
+            return nullptr;
+        }
+
+        for (const BigBuffer::Block& b : buffer) {
+            result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size);
+            if (result != 0) {
+                return nullptr;
+            }
+        }
+
+        result = mWriter->FinishEntry();
+        if (result != 0) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size()));
+        return mEntries.back().get();
+    }
+
+    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
+                             size_t offset, size_t len) override {
+        if (!mWriter) {
+            return nullptr;
+        }
+
+        size_t zipFlags = 0;
+        if (flags & ArchiveEntry::kCompress) {
+            zipFlags |= ZipWriter::kCompress;
+        }
+
+        if (flags & ArchiveEntry::kAlign) {
+            zipFlags |= ZipWriter::kAlign32;
+        }
+
+        int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+        if (result != 0) {
+            return nullptr;
+        }
+
+        result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len);
+        if (result != 0) {
+            return nullptr;
+        }
+
+        result = mWriter->FinishEntry();
+        if (result != 0) {
+            return nullptr;
+        }
+
+        mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len));
+        return mEntries.back().get();
+    }
+
+    virtual ~ZipFileWriter() {
+        if (mWriter) {
+            mWriter->Finish();
+            fclose(mFile);
+        }
+    }
+};
+
+} // namespace
+
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) {
+    return util::make_unique<DirectoryWriter>(path);
+}
+
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) {
+    return util::make_unique<ZipFileWriter>(path);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
new file mode 100644
index 0000000..c4ddeb3
--- /dev/null
+++ b/tools/aapt2/flatten/Archive.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_ARCHIVE_H
+#define AAPT_FLATTEN_ARCHIVE_H
+
+#include "util/BigBuffer.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+struct ArchiveEntry {
+    enum : uint32_t {
+        kCompress = 0x01,
+        kAlign    = 0x02,
+    };
+
+    std::string path;
+    uint32_t flags;
+    size_t uncompressedSize;
+};
+
+struct IArchiveWriter {
+    virtual ~IArchiveWriter() = default;
+
+    virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                                     const BigBuffer& buffer) = 0;
+    virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
+                                     android::FileMap* fileMap, size_t offset, size_t len) = 0;
+};
+
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path);
+
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path);
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_ARCHIVE_H */
diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h
new file mode 100644
index 0000000..de1d87a
--- /dev/null
+++ b/tools/aapt2/flatten/ChunkWriter.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_CHUNKWRITER_H
+#define AAPT_FLATTEN_CHUNKWRITER_H
+
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+class ChunkWriter {
+private:
+    BigBuffer* mBuffer;
+    size_t mStartSize = 0;
+    android::ResChunk_header* mHeader = nullptr;
+
+public:
+    explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {
+    }
+
+    ChunkWriter(const ChunkWriter&) = delete;
+    ChunkWriter& operator=(const ChunkWriter&) = delete;
+    ChunkWriter(ChunkWriter&&) = default;
+    ChunkWriter& operator=(ChunkWriter&&) = default;
+
+    template <typename T>
+    inline T* startChunk(uint16_t type) {
+        mStartSize = mBuffer->size();
+        T* chunk = mBuffer->nextBlock<T>();
+        mHeader = &chunk->header;
+        mHeader->type = util::hostToDevice16(type);
+        mHeader->headerSize = util::hostToDevice16(sizeof(T));
+        return chunk;
+    }
+
+    template <typename T>
+    inline T* nextBlock(size_t count = 1) {
+        return mBuffer->nextBlock<T>(count);
+    }
+
+    inline BigBuffer* getBuffer() {
+        return mBuffer;
+    }
+
+    inline android::ResChunk_header* getChunkHeader() {
+        return mHeader;
+    }
+
+    inline size_t size() {
+        return mBuffer->size() - mStartSize;
+    }
+
+    inline android::ResChunk_header* finish() {
+        mBuffer->align4();
+        mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
+        return mHeader;
+    }
+};
+
+template <>
+inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) {
+    mStartSize = mBuffer->size();
+    mHeader = mBuffer->nextBlock<android::ResChunk_header>();
+    mHeader->type = util::hostToDevice16(type);
+    mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
+    return mHeader;
+}
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
diff --git a/tools/aapt2/flatten/FileExportWriter.h b/tools/aapt2/flatten/FileExportWriter.h
new file mode 100644
index 0000000..7688fa7
--- /dev/null
+++ b/tools/aapt2/flatten/FileExportWriter.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_FILEEXPORTWRITER_H
+#define AAPT_FLATTEN_FILEEXPORTWRITER_H
+
+#include "StringPool.h"
+
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/ChunkWriter.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/misc.h>
+
+namespace aapt {
+
+static ChunkWriter wrapBufferWithFileExportHeader(BigBuffer* buffer, ResourceFile* res) {
+    ChunkWriter fileExportWriter(buffer);
+    FileExport_header* fileExport = fileExportWriter.startChunk<FileExport_header>(
+            RES_FILE_EXPORT_TYPE);
+
+    ExportedSymbol* symbolRefs = nullptr;
+    if (!res->exportedSymbols.empty()) {
+        symbolRefs = fileExportWriter.nextBlock<ExportedSymbol>(
+                res->exportedSymbols.size());
+    }
+    fileExport->exportedSymbolCount = util::hostToDevice32(res->exportedSymbols.size());
+
+    StringPool symbolExportPool;
+    memcpy(fileExport->magic, "AAPT", NELEM(fileExport->magic));
+    fileExport->config = res->config;
+    fileExport->config.swapHtoD();
+    fileExport->name.index = util::hostToDevice32(symbolExportPool.makeRef(res->name.toString())
+                                                  .getIndex());
+    fileExport->source.index = util::hostToDevice32(symbolExportPool.makeRef(util::utf8ToUtf16(
+            res->source.path)).getIndex());
+
+    for (const SourcedResourceName& name : res->exportedSymbols) {
+        symbolRefs->name.index = util::hostToDevice32(symbolExportPool.makeRef(name.name.toString())
+                                                      .getIndex());
+        symbolRefs->line = util::hostToDevice32(name.line);
+        symbolRefs++;
+    }
+
+    StringPool::flattenUtf16(fileExportWriter.getBuffer(), symbolExportPool);
+    return fileExportWriter;
+}
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_FILEEXPORTWRITER_H */
diff --git a/tools/aapt2/flatten/FileExportWriter_test.cpp b/tools/aapt2/flatten/FileExportWriter_test.cpp
new file mode 100644
index 0000000..32fc203
--- /dev/null
+++ b/tools/aapt2/flatten/FileExportWriter_test.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+
+#include "flatten/FileExportWriter.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(FileExportWriterTest, FlattenResourceFileDataWithNoExports) {
+    ResourceFile resFile = {
+            test::parseNameOrDie(u"@android:layout/main.xml"),
+            test::parseConfigOrDie("sw600dp-v4"),
+            Source{ "res/layout/main.xml" },
+    };
+
+    BigBuffer buffer(1024);
+    ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
+    *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
+    writer.finish();
+
+    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+
+    // There should be more data (string pool) besides the header and our data.
+    ASSERT_GT(buffer.size(), sizeof(FileExport_header) + sizeof(uint32_t));
+
+    // Write at the end of this chunk is our data.
+    uint32_t* val = (uint32_t*)(data.get() + buffer.size()) - 1;
+    EXPECT_EQ(*val, 42u);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
similarity index 73%
rename from tools/aapt2/ResourceTypeExtensions.h
rename to tools/aapt2/flatten/ResourceTypeExtensions.h
index dcbe923..38cda09 100644
--- a/tools/aapt2/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -30,6 +30,12 @@
  * future collisions.
  */
 enum {
+    /**
+     * A chunk that contains an entire file that
+     * has been compiled.
+     */
+    RES_FILE_EXPORT_TYPE = 0x000c,
+
     RES_TABLE_PUBLIC_TYPE = 0x000d,
 
     /**
@@ -60,6 +66,48 @@
     };
 };
 
+/**
+ * Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool.
+ */
+struct FileExport_header {
+    android::ResChunk_header header;
+
+    /**
+     * MAGIC value. Must be 'AAPT' (0x41415054)
+     */
+    uint8_t magic[4];
+
+    /**
+     * Version of AAPT that built this file.
+     */
+    uint32_t version;
+
+    /**
+     * The resource name.
+     */
+    android::ResStringPool_ref name;
+
+    /**
+     * Configuration of this file.
+     */
+    android::ResTable_config config;
+
+    /**
+     * Original source path of this file.
+     */
+    android::ResStringPool_ref source;
+
+    /**
+     * Number of symbols exported by this file.
+     */
+    uint32_t exportedSymbolCount;
+};
+
+struct ExportedSymbol {
+    android::ResStringPool_ref name;
+    uint32_t line;
+};
+
 struct Public_header {
     android::ResChunk_header header;
 
@@ -86,7 +134,14 @@
 
 struct Public_entry {
     uint16_t entryId;
-    uint16_t res0;
+
+    enum : uint16_t {
+        kUndefined = 0,
+        kPublic = 1,
+        kPrivate = 2,
+    };
+
+    uint16_t state;
     android::ResStringPool_ref key;
     android::ResStringPool_ref source;
     uint32_t sourceLine;
@@ -142,6 +197,16 @@
     uint32_t line;
 };
 
+/**
+ * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
+ * struct.
+ */
+struct ResTable_entry_ext {
+    android::ResTable_entry entry;
+    android::ResTable_ref parent;
+    uint32_t count;
+};
+
 } // namespace aapt
 
 #endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
new file mode 100644
index 0000000..095552a
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/TableFlattener.h"
+#include "util/BigBuffer.h"
+
+#include <base/macros.h>
+#include <type_traits>
+#include <numeric>
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+template <typename T>
+static bool cmpIds(const T* a, const T* b) {
+    return a->id.value() < b->id.value();
+}
+
+static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
+    if (len == 0) {
+        return;
+    }
+
+    size_t i;
+    const char16_t* srcData = src.data();
+    for (i = 0; i < len - 1 && i < src.size(); i++) {
+        dst[i] = util::hostToDevice16((uint16_t) srcData[i]);
+    }
+    dst[i] = 0;
+}
+
+struct FlatEntry {
+    ResourceEntry* entry;
+    Value* value;
+    uint32_t entryKey;
+    uint32_t sourcePathKey;
+    uint32_t sourceLine;
+};
+
+class SymbolWriter {
+public:
+    struct Entry {
+        StringPool::Ref name;
+        size_t offset;
+    };
+
+    std::vector<Entry> symbols;
+
+    explicit SymbolWriter(StringPool* pool) : mPool(pool) {
+    }
+
+    void addSymbol(const ResourceNameRef& name, size_t offset) {
+        symbols.push_back(Entry{ mPool->makeRef(name.package.toString() + u":" +
+                                               toString(name.type).toString() + u"/" +
+                                               name.entry.toString()), offset });
+    }
+
+    void shiftAllOffsets(size_t offset) {
+        for (Entry& entry : symbols) {
+            entry.offset += offset;
+        }
+    }
+
+private:
+    StringPool* mPool;
+};
+
+struct MapFlattenVisitor : public RawValueVisitor {
+    using RawValueVisitor::visit;
+
+    SymbolWriter* mSymbols;
+    FlatEntry* mEntry;
+    BigBuffer* mBuffer;
+    size_t mEntryCount = 0;
+    Maybe<uint32_t> mParentIdent;
+    Maybe<ResourceNameRef> mParentName;
+
+    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
+            mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
+    }
+
+    void flattenKey(Reference* key, ResTable_map* outEntry) {
+        if (!key->id) {
+            assert(key->name && "reference must have a name");
+
+            outEntry->name.ident = util::hostToDevice32(0);
+            mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                                    offsetof(ResTable_map, name));
+        } else {
+            outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+        }
+    }
+
+    void flattenValue(Item* value, ResTable_map* outEntry) {
+        if (Reference* ref = valueCast<Reference>(value)) {
+            if (!ref->id) {
+                assert(ref->name && "reference must have a name");
+
+                mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                                        offsetof(ResTable_map, value) + offsetof(Res_value, data));
+            }
+        }
+
+        bool result = value->flatten(&outEntry->value);
+        assert(result && "flatten failed");
+    }
+
+    void flattenEntry(Reference* key, Item* value) {
+        ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+        flattenKey(key, outEntry);
+        flattenValue(value, outEntry);
+        outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+        mEntryCount++;
+    }
+
+    void visit(Attribute* attr) override {
+        {
+            Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
+            flattenEntry(&key, &val);
+        }
+
+        for (Attribute::Symbol& s : attr->symbols) {
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+            flattenEntry(&s.symbol, &val);
+        }
+    }
+
+    static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
+        if (a.key.id) {
+            if (b.key.id) {
+                return a.key.id.value() < b.key.id.value();
+            }
+            return true;
+        } else if (!b.key.id) {
+            return a.key.name.value() < b.key.name.value();
+        }
+        return false;
+    }
+
+    void visit(Style* style) override {
+        if (style->parent) {
+            if (!style->parent.value().id) {
+                assert(style->parent.value().name && "reference must have a name");
+                mParentName = style->parent.value().name;
+            } else {
+                mParentIdent = style->parent.value().id.value().id;
+            }
+        }
+
+        // Sort the style.
+        std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
+
+        for (Style::Entry& entry : style->entries) {
+            flattenEntry(&entry.key, entry.value.get());
+        }
+    }
+
+    void visit(Styleable* styleable) override {
+        for (auto& attrRef : styleable->entries) {
+            BinaryPrimitive val(Res_value{});
+            flattenEntry(&attrRef, &val);
+        }
+    }
+
+    void visit(Array* array) override {
+        for (auto& item : array->items) {
+            ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+            flattenValue(item.get(), outEntry);
+            outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+            mEntryCount++;
+        }
+    }
+
+    void visit(Plural* plural) override {
+        const size_t count = plural->values.size();
+        for (size_t i = 0; i < count; i++) {
+            if (!plural->values[i]) {
+                continue;
+            }
+
+            ResourceId q;
+            switch (i) {
+            case Plural::Zero:
+                q.id = android::ResTable_map::ATTR_ZERO;
+                break;
+
+            case Plural::One:
+                q.id = android::ResTable_map::ATTR_ONE;
+                break;
+
+            case Plural::Two:
+                q.id = android::ResTable_map::ATTR_TWO;
+                break;
+
+            case Plural::Few:
+                q.id = android::ResTable_map::ATTR_FEW;
+                break;
+
+            case Plural::Many:
+                q.id = android::ResTable_map::ATTR_MANY;
+                break;
+
+            case Plural::Other:
+                q.id = android::ResTable_map::ATTR_OTHER;
+                break;
+
+            default:
+                assert(false);
+                break;
+            }
+
+            Reference key(q);
+            flattenEntry(&key, plural->values[i].get());
+        }
+    }
+};
+
+class PackageFlattener {
+public:
+    PackageFlattener(IDiagnostics* diag, TableFlattenerOptions options,
+                     ResourceTablePackage* package, SymbolWriter* symbolWriter,
+                     StringPool* sourcePool) :
+            mDiag(diag), mOptions(options), mPackage(package), mSymbols(symbolWriter),
+            mSourcePool(sourcePool) {
+    }
+
+    bool flattenPackage(BigBuffer* buffer) {
+        ChunkWriter pkgWriter(buffer);
+        ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
+                RES_TABLE_PACKAGE_TYPE);
+        pkgHeader->id = util::hostToDevice32(mPackage->id.value());
+
+        if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
+            mDiag->error(DiagMessage() <<
+                         "package name '" << mPackage->name << "' is too long");
+            return false;
+        }
+
+        // Copy the package name in device endianness.
+        strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), mPackage->name);
+
+        // Serialize the types. We do this now so that our type and key strings
+        // are populated. We write those first.
+        BigBuffer typeBuffer(1024);
+        flattenTypes(&typeBuffer);
+
+        pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+
+        pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
+
+        // Add the ResTable_package header/type/key strings to the offset.
+        mSymbols->shiftAllOffsets(pkgWriter.size());
+
+        // Append the types.
+        buffer->appendBuffer(std::move(typeBuffer));
+
+        pkgWriter.finish();
+        return true;
+    }
+
+private:
+    IDiagnostics* mDiag;
+    TableFlattenerOptions mOptions;
+    ResourceTablePackage* mPackage;
+    StringPool mTypePool;
+    StringPool mKeyPool;
+    SymbolWriter* mSymbols;
+    StringPool* mSourcePool;
+
+    template <typename T>
+    T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
+        static_assert(std::is_same<ResTable_entry, T>::value ||
+                      std::is_same<ResTable_entry_ext, T>::value,
+                      "T must be ResTable_entry or ResTable_entry_ext");
+
+        T* result = buffer->nextBlock<T>();
+        ResTable_entry* outEntry = (ResTable_entry*)(result);
+        if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
+            outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
+        }
+
+        if (entry->value->isWeak()) {
+            outEntry->flags |= ResTable_entry::FLAG_WEAK;
+        }
+
+        if (!entry->value->isItem()) {
+            outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
+        }
+
+        outEntry->key.index = util::hostToDevice32(entry->entryKey);
+        outEntry->size = sizeof(T);
+
+        if (mOptions.useExtendedChunks) {
+            // Write the extra source block. This will be ignored by the Android runtime.
+            ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
+            sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey);
+            sourceBlock->line = util::hostToDevice32(entry->sourceLine);
+            outEntry->size += sizeof(*sourceBlock);
+        }
+
+        outEntry->flags = util::hostToDevice16(outEntry->flags);
+        outEntry->size = util::hostToDevice16(outEntry->size);
+        return result;
+    }
+
+    bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
+        if (entry->value->isItem()) {
+            writeEntry<ResTable_entry>(entry, buffer);
+            if (Reference* ref = valueCast<Reference>(entry->value)) {
+                if (!ref->id) {
+                    assert(ref->name && "reference must have at least a name");
+                    mSymbols->addSymbol(ref->name.value(),
+                                        buffer->size() + offsetof(Res_value, data));
+                }
+            }
+            Res_value* outValue = buffer->nextBlock<Res_value>();
+            bool result = static_cast<Item*>(entry->value)->flatten(outValue);
+            assert(result && "flatten failed");
+            outValue->size = util::hostToDevice16(sizeof(*outValue));
+        } else {
+            const size_t beforeEntry = buffer->size();
+            ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
+            MapFlattenVisitor visitor(mSymbols, entry, buffer);
+            entry->value->accept(&visitor);
+            outEntry->count = util::hostToDevice32(visitor.mEntryCount);
+            if (visitor.mParentName) {
+                mSymbols->addSymbol(visitor.mParentName.value(),
+                                    beforeEntry + offsetof(ResTable_entry_ext, parent));
+            } else if (visitor.mParentIdent) {
+                outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value());
+            }
+        }
+        return true;
+    }
+
+    bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config,
+                       std::vector<FlatEntry>* entries, BigBuffer* buffer) {
+        ChunkWriter typeWriter(buffer);
+        ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+        typeHeader->id = type->id.value();
+        typeHeader->config = config;
+        typeHeader->config.swapHtoD();
+
+        auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
+            return std::max(max, (uint32_t) a->id.value());
+        };
+
+        // Find the largest entry ID. That is how many entries we will have.
+        const uint32_t entryCount =
+                std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1;
+
+        typeHeader->entryCount = util::hostToDevice32(entryCount);
+        uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
+
+        assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1);
+        memset(indices, 0xff, entryCount * sizeof(uint32_t));
+
+        typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
+
+        const size_t entryStart = typeWriter.getBuffer()->size();
+        for (FlatEntry& flatEntry : *entries) {
+            assert(flatEntry.entry->id.value() < entryCount);
+            indices[flatEntry.entry->id.value()] = util::hostToDevice32(
+                    typeWriter.getBuffer()->size() - entryStart);
+            if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
+                mDiag->error(DiagMessage()
+                             << "failed to flatten resource '"
+                             << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name)
+                             << "' for configuration '" << config << "'");
+                return false;
+            }
+        }
+        typeWriter.finish();
+        return true;
+    }
+
+    std::vector<ResourceTableType*> collectAndSortTypes() {
+        std::vector<ResourceTableType*> sortedTypes;
+        for (auto& type : mPackage->types) {
+            if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
+                // Styleables aren't real Resource Types, they are represented in the R.java
+                // file.
+                continue;
+            }
+
+            assert(type->id && "type must have an ID set");
+
+            sortedTypes.push_back(type.get());
+        }
+        std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
+        return sortedTypes;
+    }
+
+    std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
+        // Sort the entries by entry ID.
+        std::vector<ResourceEntry*> sortedEntries;
+        for (auto& entry : type->entries) {
+            assert(entry->id && "entry must have an ID set");
+            sortedEntries.push_back(entry.get());
+        }
+        std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
+        return sortedEntries;
+    }
+
+    bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
+                         BigBuffer* buffer) {
+        ChunkWriter typeSpecWriter(buffer);
+        ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>(
+                RES_TABLE_TYPE_SPEC_TYPE);
+        specHeader->id = type->id.value();
+
+        if (sortedEntries->empty()) {
+            typeSpecWriter.finish();
+            return true;
+        }
+
+        // We can't just take the size of the vector. There may be holes in the entry ID space.
+        // Since the entries are sorted by ID, the last one will be the biggest.
+        const size_t numEntries = sortedEntries->back()->id.value() + 1;
+
+        specHeader->entryCount = util::hostToDevice32(numEntries);
+
+        // Reserve space for the masks of each resource in this type. These
+        // show for which configuration axis the resource changes.
+        uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
+
+        const size_t actualNumEntries = sortedEntries->size();
+        for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
+            ResourceEntry* entry = sortedEntries->at(entryIndex);
+
+            // Populate the config masks for this entry.
+
+            if (entry->symbolStatus.state == SymbolState::kPublic) {
+                configMasks[entry->id.value()] |=
+                        util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+            }
+
+            const size_t configCount = entry->values.size();
+            for (size_t i = 0; i < configCount; i++) {
+                const ConfigDescription& config = entry->values[i].config;
+                for (size_t j = i + 1; j < configCount; j++) {
+                    configMasks[entry->id.value()] |= util::hostToDevice32(
+                            config.diff(entry->values[j].config));
+                }
+            }
+        }
+        typeSpecWriter.finish();
+        return true;
+    }
+
+    bool flattenPublic(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
+                       BigBuffer* buffer) {
+        ChunkWriter publicWriter(buffer);
+        Public_header* publicHeader = publicWriter.startChunk<Public_header>(RES_TABLE_PUBLIC_TYPE);
+        publicHeader->typeId = type->id.value();
+
+        for (ResourceEntry* entry : *sortedEntries) {
+            if (entry->symbolStatus.state != SymbolState::kUndefined) {
+                // Write the public status of this entry.
+                Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
+                publicEntry->entryId = util::hostToDevice32(entry->id.value());
+                publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
+                        entry->name).getIndex());
+                publicEntry->source.index = util::hostToDevice32(mSourcePool->makeRef(
+                        util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
+                if (entry->symbolStatus.source.line) {
+                    publicEntry->sourceLine = util::hostToDevice32(
+                            entry->symbolStatus.source.line.value());
+                }
+
+                switch (entry->symbolStatus.state) {
+                case SymbolState::kPrivate:
+                    publicEntry->state = Public_entry::kPrivate;
+                    break;
+
+                case SymbolState::kPublic:
+                    publicEntry->state = Public_entry::kPublic;
+                    break;
+
+                default:
+                    assert(false && "should not serialize any other state");
+                    break;
+                }
+
+                // Don't hostToDevice until the last step.
+                publicHeader->count += 1;
+            }
+        }
+
+        publicHeader->count = util::hostToDevice32(publicHeader->count);
+        publicWriter.finish();
+        return true;
+    }
+
+    bool flattenTypes(BigBuffer* buffer) {
+        // Sort the types by their IDs. They will be inserted into the StringPool in this order.
+        std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
+
+        size_t expectedTypeId = 1;
+        for (ResourceTableType* type : sortedTypes) {
+            // If there is a gap in the type IDs, fill in the StringPool
+            // with empty values until we reach the ID we expect.
+            while (type->id.value() > expectedTypeId) {
+                std::u16string typeName(u"?");
+                typeName += expectedTypeId;
+                mTypePool.makeRef(typeName);
+                expectedTypeId++;
+            }
+            expectedTypeId++;
+            mTypePool.makeRef(toString(type->type));
+
+            std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
+
+            if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
+                return false;
+            }
+
+            if (mOptions.useExtendedChunks) {
+                if (!flattenPublic(type, &sortedEntries, buffer)) {
+                    return false;
+                }
+            }
+
+            // The binary resource table lists resource entries for each configuration.
+            // We store them inverted, where a resource entry lists the values for each
+            // configuration available. Here we reverse this to match the binary table.
+            std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
+            for (ResourceEntry* entry : sortedEntries) {
+                const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
+
+                // Group values by configuration.
+                for (auto& configValue : entry->values) {
+                   configToEntryListMap[configValue.config].push_back(FlatEntry{
+                            entry, configValue.value.get(), (uint32_t) keyIndex,
+                            (uint32_t)(mSourcePool->makeRef(util::utf8ToUtf16(
+                                    configValue.source.path)).getIndex()),
+                            (uint32_t)(configValue.source.line
+                                    ? configValue.source.line.value() : 0)
+                   });
+                }
+            }
+
+            // Flatten a configuration value.
+            for (auto& entry : configToEntryListMap) {
+                if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+};
+
+} // namespace
+
+bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
+    // We must do this before writing the resources, since the string pool IDs may change.
+    table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+        int diff = a.context.priority - b.context.priority;
+        if (diff < 0) return true;
+        if (diff > 0) return false;
+        diff = a.context.config.compare(b.context.config);
+        if (diff < 0) return true;
+        if (diff > 0) return false;
+        return a.value < b.value;
+    });
+    table->stringPool.prune();
+
+    // Write the ResTable header.
+    ChunkWriter tableWriter(mBuffer);
+    ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
+    tableHeader->packageCount = util::hostToDevice32(table->packages.size());
+
+    // Flatten the values string pool.
+    StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
+
+    // If we have a reference to a symbol that doesn't exist, we don't know its resource ID.
+    // We encode the name of the symbol along with the offset of where to include the resource ID
+    // once it is found.
+    StringPool symbolPool;
+    std::vector<SymbolWriter::Entry> symbolOffsets;
+
+    // String pool holding the source paths of each value.
+    StringPool sourcePool;
+
+    BigBuffer packageBuffer(1024);
+
+    // Flatten each package.
+    for (auto& package : table->packages) {
+        const size_t beforePackageSize = packageBuffer.size();
+
+        // All packages will share a single global symbol pool.
+        SymbolWriter packageSymbolWriter(&symbolPool);
+
+        PackageFlattener flattener(context->getDiagnostics(), mOptions, package.get(),
+                                   &packageSymbolWriter, &sourcePool);
+        if (!flattener.flattenPackage(&packageBuffer)) {
+            return false;
+        }
+
+        // The symbols are offset only from their own Package start. Offset them from the
+        // start of the packageBuffer.
+        packageSymbolWriter.shiftAllOffsets(beforePackageSize);
+
+        // Extract all the symbols to offset
+        symbolOffsets.insert(symbolOffsets.end(),
+                             std::make_move_iterator(packageSymbolWriter.symbols.begin()),
+                             std::make_move_iterator(packageSymbolWriter.symbols.end()));
+    }
+
+    SymbolTable_entry* symbolEntryData = nullptr;
+    if (mOptions.useExtendedChunks) {
+        if (!symbolOffsets.empty()) {
+            // Sort the offsets so we can scan them linearly.
+            std::sort(symbolOffsets.begin(), symbolOffsets.end(),
+                      [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
+                          return a.offset < b.offset;
+                      });
+
+            // Write the Symbol header.
+            ChunkWriter symbolWriter(tableWriter.getBuffer());
+            SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
+                    RES_TABLE_SYMBOL_TABLE_TYPE);
+            symbolHeader->count = util::hostToDevice32(symbolOffsets.size());
+
+            symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(symbolOffsets.size());
+            StringPool::flattenUtf8(symbolWriter.getBuffer(), symbolPool);
+            symbolWriter.finish();
+        }
+
+        if (sourcePool.size() > 0) {
+            // Write out source pool.
+            ChunkWriter srcWriter(tableWriter.getBuffer());
+            srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
+            StringPool::flattenUtf8(srcWriter.getBuffer(), sourcePool);
+            srcWriter.finish();
+        }
+    }
+
+    const size_t beforePackagesSize = tableWriter.size();
+
+    // Finally merge all the packages into the main buffer.
+    tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
+
+    // Update the offsets to their final values.
+    if (symbolEntryData) {
+        for (SymbolWriter::Entry& entry : symbolOffsets) {
+            symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
+
+            // The symbols were all calculated with the packageBuffer offset. We need to
+            // add the beginning of the output buffer.
+            symbolEntryData->offset = util::hostToDevice32(entry.offset + beforePackagesSize);
+            symbolEntryData++;
+        }
+    }
+
+    tableWriter.finish();
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
new file mode 100644
index 0000000..901b129
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_TABLEFLATTENER_H
+#define AAPT_FLATTEN_TABLEFLATTENER_H
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+class BigBuffer;
+class ResourceTable;
+
+struct TableFlattenerOptions {
+    /**
+     * Specifies whether to output extended chunks, like
+     * source information and missing symbol entries. Default
+     * is false.
+     *
+     * Set this to true when emitting intermediate resource table.
+     */
+    bool useExtendedChunks = false;
+};
+
+class TableFlattener : public IResourceTableConsumer {
+public:
+    TableFlattener(BigBuffer* buffer, TableFlattenerOptions options) :
+            mBuffer(buffer), mOptions(options) {
+    }
+
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+
+private:
+    BigBuffer* mBuffer;
+    TableFlattenerOptions mOptions;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
new file mode 100644
index 0000000..68a1f47
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatten/TableFlattener.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace aapt {
+
+class TableFlattenerTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"com.app.test")
+                .setPackageId(0x7f)
+                .build();
+    }
+
+    ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
+        BigBuffer buffer(1024);
+        TableFlattenerOptions options = {};
+        options.useExtendedChunks = true;
+        TableFlattener flattener(&buffer, options);
+        if (!flattener.consume(mContext.get(), table)) {
+            return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+        }
+
+        std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+        if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
+            return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+    ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) {
+        BigBuffer buffer(1024);
+        TableFlattenerOptions options = {};
+        options.useExtendedChunks = true;
+        TableFlattener flattener(&buffer, options);
+        if (!flattener.consume(mContext.get(), table)) {
+            return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+        }
+
+        std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+        BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size());
+        if (!parser.parse()) {
+            return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+    ::testing::AssertionResult exists(ResTable* table,
+                                      const StringPiece16& expectedName,
+                                      const ResourceId expectedId,
+                                      const ConfigDescription& expectedConfig,
+                                      const uint8_t expectedDataType, const uint32_t expectedData,
+                                      const uint32_t expectedSpecFlags) {
+        const ResourceName expectedResName = test::parseNameOrDie(expectedName);
+
+        table->setParameters(&expectedConfig);
+
+        ResTable_config config;
+        Res_value val;
+        uint32_t specFlags;
+        if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) {
+            return ::testing::AssertionFailure() << "could not find resource with";
+        }
+
+        if (expectedDataType != val.dataType) {
+            return ::testing::AssertionFailure()
+                    << "expected data type "
+                    << std::hex << (int) expectedDataType << " but got data type "
+                    << (int) val.dataType << std::dec << " instead";
+        }
+
+        if (expectedData != val.data) {
+            return ::testing::AssertionFailure()
+                    << "expected data "
+                    << std::hex << expectedData << " but got data "
+                    << val.data << std::dec << " instead";
+        }
+
+        if (expectedSpecFlags != specFlags) {
+            return ::testing::AssertionFailure()
+                    << "expected specFlags "
+                    << std::hex << expectedSpecFlags << " but got specFlags "
+                    << specFlags << std::dec << " instead";
+        }
+
+        ResTable::resource_name actualName;
+        if (!table->getResourceName(expectedId.id, false, &actualName)) {
+            return ::testing::AssertionFailure() << "failed to find resource name";
+        }
+
+        StringPiece16 package16(actualName.package, actualName.packageLen);
+        if (package16 != expectedResName.package) {
+            return ::testing::AssertionFailure()
+                    << "expected package '" << expectedResName.package << "' but got '"
+                    << package16 << "'";
+        }
+
+        StringPiece16 type16(actualName.type, actualName.typeLen);
+        if (type16 != toString(expectedResName.type)) {
+            return ::testing::AssertionFailure()
+                    << "expected type '" << expectedResName.type
+                    << "' but got '" << type16 << "'";
+        }
+
+        StringPiece16 name16(actualName.name, actualName.nameLen);
+        if (name16 != expectedResName.entry) {
+            return ::testing::AssertionFailure()
+                    << "expected name '" << expectedResName.entry
+                    << "' but got '" << name16 << "'";
+        }
+
+        if (expectedConfig != config) {
+            return ::testing::AssertionFailure()
+                    << "expected config '" << expectedConfig << "' but got '"
+                    << ConfigDescription(config) << "'";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+private:
+    std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020000))
+            .addSimple(u"@com.app.test:id/two", ResourceId(0x7f020001))
+            .addValue(u"@com.app.test:id/three", ResourceId(0x7f020002),
+                      test::buildReference(u"@com.app.test:id/one", ResourceId(0x7f020000)))
+            .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
+            .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                      test::parseConfigOrDie("v1"),
+                      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
+            .addString(u"@com.app.test:string/test", ResourceId(0x7f040000), u"foo")
+            .addString(u"@com.app.test:layout/bar", ResourceId(0x7f050000), u"res/layout/bar.xml")
+            .build();
+
+    ResTable resTable;
+    ASSERT_TRUE(flatten(table.get(), &resTable));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020000), {},
+                       Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/two", ResourceId(0x7f020001), {},
+                       Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020002), {},
+                       Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                       {}, Res_value::TYPE_INT_DEC, 1u,
+                       ResTable_config::CONFIG_VERSION));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000),
+                       test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
+                       ResTable_config::CONFIG_VERSION));
+
+    StringPiece16 fooStr = u"foo";
+    ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size());
+    ASSERT_GE(idx, 0);
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:string/test", ResourceId(0x7f040000),
+                       {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+
+    StringPiece16 barPath = u"res/layout/bar.xml";
+    idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size());
+    ASSERT_GE(idx, 0);
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:layout/bar", ResourceId(0x7f050000), {},
+                       Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020001))
+            .addSimple(u"@com.app.test:id/three", ResourceId(0x7f020003))
+            .build();
+
+    ResTable resTable;
+    ASSERT_TRUE(flatten(table.get(), &resTable));
+
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020001), {},
+                       Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+    EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020003), {},
+                           Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenUnlinkedTable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:integer/one", ResourceId(0x7f020000),
+                      test::buildReference(u"@android:integer/foo"))
+            .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f030000), test::StyleBuilder()
+                    .setParent(u"@android:style/Theme.Material")
+                    .addItem(u"@android:attr/background", {})
+                    .addItem(u"@android:attr/colorAccent",
+                             test::buildReference(u"@com.app.test:color/green"))
+                    .build())
+            .build();
+
+    {
+        // Need access to stringPool to make RawString.
+        Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+        style->entries[0].value = util::make_unique<RawString>(table->stringPool.makeRef(u"foo"));
+    }
+
+    ResourceTable finalTable;
+    ASSERT_TRUE(flatten(table.get(), &finalTable));
+
+    Reference* ref = test::getValue<Reference>(&finalTable, u"@com.app.test:integer/one");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@android:integer/foo"));
+
+    Style* style = test::getValue<Style>(&finalTable, u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().name);
+    EXPECT_EQ(style->parent.value().name.value(),
+              test::parseNameOrDie(u"@android:style/Theme.Material"));
+
+    ASSERT_EQ(2u, style->entries.size());
+
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+              test::parseNameOrDie(u"@android:attr/background"));
+    RawString* raw = valueCast<RawString>(style->entries[0].value.get());
+    ASSERT_NE(raw, nullptr);
+    EXPECT_EQ(*raw->value, u"foo");
+
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+              test::parseNameOrDie(u"@android:attr/colorAccent"));
+    ref = valueCast<Reference>(style->entries[1].value.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
new file mode 100644
index 0000000..4efb08b
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SdkConstants.h"
+#include "XmlDom.h"
+
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/XmlFlattener.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <vector>
+#include <utils/misc.h>
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+constexpr uint32_t kLowPriority = 0xffffffffu;
+
+struct XmlFlattenerVisitor : public xml::Visitor {
+    using xml::Visitor::visit;
+
+    BigBuffer* mBuffer;
+    XmlFlattenerOptions mOptions;
+    StringPool mPool;
+    std::map<uint8_t, StringPool> mPackagePools;
+
+    struct StringFlattenDest {
+        StringPool::Ref ref;
+        ResStringPool_ref* dest;
+    };
+    std::vector<StringFlattenDest> mStringRefs;
+
+    // Scratch vector to filter attributes. We avoid allocations
+    // making this a member.
+    std::vector<xml::Attribute*> mFilteredAttrs;
+
+
+    XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
+            mBuffer(buffer), mOptions(options) {
+    }
+
+    void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
+        if (!str.empty()) {
+            mStringRefs.push_back(StringFlattenDest{
+                    mPool.makeRef(str, StringPool::Context{ priority }),
+                    dest });
+        } else {
+            // The device doesn't think a string of size 0 is the same as null.
+            dest->index = util::deviceToHost32(-1);
+        }
+    }
+
+    void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+        mStringRefs.push_back(StringFlattenDest{ ref, dest });
+    }
+
+    void writeNamespace(xml::Namespace* node, uint16_t type) {
+        ChunkWriter writer(mBuffer);
+
+        ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
+        flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+        flatNode->comment.index = util::hostToDevice32(-1);
+
+        ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
+        addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
+        addString(node->namespaceUri, kLowPriority, &flatNs->uri);
+
+        writer.finish();
+    }
+
+    void visit(xml::Namespace* node) override {
+        writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
+        xml::Visitor::visit(node);
+        writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
+    }
+
+    void visit(xml::Text* node) override {
+        if (util::trimWhitespace(node->text).empty()) {
+            // Skip whitespace only text nodes.
+            return;
+        }
+
+        ChunkWriter writer(mBuffer);
+        ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
+        flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+        flatNode->comment.index = util::hostToDevice32(-1);
+
+        ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
+        addString(node->text, kLowPriority, &flatText->data);
+
+        writer.finish();
+    }
+
+    void visit(xml::Element* node) override {
+        {
+            ChunkWriter startWriter(mBuffer);
+            ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
+                    RES_XML_START_ELEMENT_TYPE);
+            flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+            flatNode->comment.index = util::hostToDevice32(-1);
+
+            ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
+            addString(node->namespaceUri, kLowPriority, &flatElem->ns);
+            addString(node->name, kLowPriority, &flatElem->name);
+            flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
+            flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
+
+            writeAttributes(node, flatElem, &startWriter);
+
+            startWriter.finish();
+        }
+
+        xml::Visitor::visit(node);
+
+        {
+            ChunkWriter endWriter(mBuffer);
+            ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
+                    RES_XML_END_ELEMENT_TYPE);
+            flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
+            flatEndNode->comment.index = util::hostToDevice32(-1);
+
+            ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
+            addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
+            addString(node->name, kLowPriority, &flatEndElem->name);
+
+            endWriter.finish();
+        }
+    }
+
+    static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
+        if (a->compiledAttribute) {
+            if (b->compiledAttribute) {
+                return a->compiledAttribute.value().id < b->compiledAttribute.value().id;
+            }
+            return true;
+        } else if (!b->compiledAttribute) {
+            int diff = a->namespaceUri.compare(b->namespaceUri);
+            if (diff < 0) {
+                return true;
+            } else if (diff > 0) {
+                return false;
+            }
+            return a->name < b->name;
+        }
+        return false;
+    }
+
+    void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
+        mFilteredAttrs.clear();
+        mFilteredAttrs.reserve(node->attributes.size());
+
+        // Filter the attributes.
+        for (xml::Attribute& attr : node->attributes) {
+            if (mOptions.maxSdkLevel && attr.compiledAttribute) {
+                size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id);
+                if (sdkLevel > mOptions.maxSdkLevel.value()) {
+                    continue;
+                }
+            }
+            mFilteredAttrs.push_back(&attr);
+        }
+
+        if (mFilteredAttrs.empty()) {
+            return;
+        }
+
+        const ResourceId kIdAttr(0x010100d0);
+
+        std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
+
+        flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
+
+        ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
+                mFilteredAttrs.size());
+        uint16_t attributeIndex = 1;
+        for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
+            // Assign the indices for specific attributes.
+            if (xmlAttr->compiledAttribute &&
+                    xmlAttr->compiledAttribute.value().id == kIdAttr) {
+                flatElem->idIndex = util::hostToDevice16(attributeIndex);
+            } else if (xmlAttr->namespaceUri.empty()) {
+                if (xmlAttr->name == u"class") {
+                    flatElem->classIndex = util::hostToDevice16(attributeIndex);
+                } else if (xmlAttr->name == u"style") {
+                    flatElem->styleIndex = util::hostToDevice16(attributeIndex);
+                }
+            }
+            attributeIndex++;
+
+            // Add the namespaceUri to the list of StringRefs to encode.
+            addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
+
+            flatAttr->rawValue.index = util::hostToDevice32(-1);
+
+            if (!xmlAttr->compiledAttribute) {
+                // The attribute has no associated ResourceID, so the string order doesn't matter.
+                addString(xmlAttr->name, kLowPriority, &flatAttr->name);
+            } else {
+                // Attribute names are stored without packages, but we use
+                // their StringPool index to lookup their resource IDs.
+                // This will cause collisions, so we can't dedupe
+                // attribute names from different packages. We use separate
+                // pools that we later combine.
+                //
+                // Lookup the StringPool for this package and make the reference there.
+                const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
+
+                StringPool::Ref nameRef = mPackagePools[aaptAttr.id.packageId()].makeRef(
+                        xmlAttr->name, StringPool::Context{ aaptAttr.id.id });
+
+                // Add it to the list of strings to flatten.
+                addString(nameRef, &flatAttr->name);
+
+                if (mOptions.keepRawValues) {
+                    // Keep raw values (this is for static libraries).
+                    // TODO(with a smarter inflater for binary XML, we can do without this).
+                    addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+                }
+            }
+
+            if (xmlAttr->compiledValue) {
+                bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
+                assert(result);
+            } else {
+                // Flatten as a regular string type.
+                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+                addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+                addString(xmlAttr->value, kLowPriority,
+                          (ResStringPool_ref*) &flatAttr->typedValue.data);
+            }
+
+            flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
+            flatAttr++;
+        }
+    }
+};
+
+} // namespace
+
+bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
+    BigBuffer nodeBuffer(1024);
+    XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
+    node->accept(&visitor);
+
+    // Merge the package pools into the main pool.
+    for (auto& packagePoolEntry : visitor.mPackagePools) {
+        visitor.mPool.merge(std::move(packagePoolEntry.second));
+    }
+
+    // Sort the string pool so that attribute resource IDs show up first.
+    visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+        return a.context.priority < b.context.priority;
+    });
+
+    // Now we flatten the string pool references into the correct places.
+    for (const auto& refEntry : visitor.mStringRefs) {
+        refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
+    }
+
+    // Write the XML header.
+    ChunkWriter xmlHeaderWriter(mBuffer);
+    xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
+
+    // Flatten the StringPool.
+    StringPool::flattenUtf16(mBuffer, visitor.mPool);
+
+    {
+        // Write the array of resource IDs, indexed by StringPool order.
+        ChunkWriter resIdMapWriter(mBuffer);
+        resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
+        for (const auto& str : visitor.mPool) {
+            ResourceId id = { str->context.priority };
+            if (id.id == kLowPriority || !id.isValid()) {
+                // When we see the first non-resource ID,
+                // we're done.
+                break;
+            }
+
+            *resIdMapWriter.nextBlock<uint32_t>() = id.id;
+        }
+        resIdMapWriter.finish();
+    }
+
+    // Move the nodeBuffer and append it to the out buffer.
+    mBuffer->appendBuffer(std::move(nodeBuffer));
+
+    // Finish the xml header.
+    xmlHeaderWriter.finish();
+    return true;
+}
+
+bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) {
+    if (!resource->root) {
+        return false;
+    }
+    return flatten(context, resource->root.get());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
new file mode 100644
index 0000000..b1fb3a7
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_XMLFLATTENER_H
+#define AAPT_FLATTEN_XMLFLATTENER_H
+
+#include "util/BigBuffer.h"
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+namespace xml {
+struct Node;
+}
+
+struct XmlFlattenerOptions {
+    /**
+     * Keep attribute raw string values along with typed values.
+     */
+    bool keepRawValues = false;
+
+    /**
+     * If set, the max SDK level of attribute to flatten. All others are ignored.
+     */
+    Maybe<size_t> maxSdkLevel;
+};
+
+class XmlFlattener : public IXmlResourceConsumer {
+public:
+    XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) :
+            mBuffer(buffer), mOptions(options) {
+    }
+
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+
+private:
+    BigBuffer* mBuffer;
+    XmlFlattenerOptions mOptions;
+
+    bool flatten(IAaptContext* context, xml::Node* node);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_XMLFLATTENER_H */
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
new file mode 100644
index 0000000..318bcdd
--- /dev/null
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatten/XmlFlattener.h"
+#include "link/Linkers.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+class XmlFlattenerTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"com.app.test")
+                .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+                .setSymbolTable(test::StaticSymbolTableBuilder()
+                        .addSymbol(u"@android:attr/id", ResourceId(0x010100d0),
+                                   test::AttributeBuilder().build())
+                        .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f020000))
+                        .addSymbol(u"@android:attr/paddingStart", ResourceId(0x010103b3),
+                                   test::AttributeBuilder().build())
+                        .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                   test::AttributeBuilder().build())
+                        .build())
+                .build();
+    }
+
+    ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree,
+                                       XmlFlattenerOptions options = {}) {
+        BigBuffer buffer(1024);
+        XmlFlattener flattener(&buffer, options);
+        if (!flattener.consume(mContext.get(), doc)) {
+            return ::testing::AssertionFailure() << "failed to flatten XML Tree";
+        }
+
+        std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+        if (outTree->setTo(data.get(), buffer.size(), true) != android::NO_ERROR) {
+            return ::testing::AssertionFailure() << "flattened XML is corrupt";
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+protected:
+    std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:test="http://com.test"
+                  attr="hey">
+              <Layout test:hello="hi" />
+              <Layout>Some text</Layout>
+            </View>)EOF");
+
+
+    android::ResXMLTree tree;
+    ASSERT_TRUE(flatten(doc.get(), &tree));
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
+
+    size_t len;
+    const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
+    EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+
+    const char16_t* namespaceUri = tree.getNamespaceUri(&len);
+    ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    const char16_t* tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"View");
+
+    ASSERT_EQ(1u, tree.getAttributeCount());
+    ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
+    const char16_t* attrName = tree.getAttributeName(0, &len);
+    EXPECT_EQ(StringPiece16(attrName, len), u"attr");
+
+    EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+
+    ASSERT_EQ(1u, tree.getAttributeCount());
+    const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
+    EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
+
+    attrName = tree.getAttributeName(0, &len);
+    EXPECT_EQ(StringPiece16(attrName, len), u"hello");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+    ASSERT_EQ(0u, tree.getAttributeCount());
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
+    const char16_t* text = tree.getText(&len);
+    EXPECT_EQ(StringPiece16(text, len), u"Some text");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+    tagName = tree.getElementName(&len);
+    EXPECT_EQ(StringPiece16(tagName, len), u"View");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
+    namespacePrefix = tree.getNamespacePrefix(&len);
+    EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+
+    namespaceUri = tree.getNamespaceUri(&len);
+    ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
+
+    ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/android"
+                android:paddingStart="1dp"
+                android:colorAccent="#ffffff"/>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+    ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
+    ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
+
+    android::ResXMLTree tree;
+    XmlFlattenerOptions options;
+    options.maxSdkLevel = 17;
+    ASSERT_TRUE(flatten(doc.get(), &tree, options));
+
+    while (tree.next() != android::ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+    }
+
+    ASSERT_EQ(1u, tree.getAttributeCount());
+    EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
+}
+
+TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:id="@id/id"
+                  class="str"
+                  style="@id/id"/>)EOF");
+
+    android::ResXMLTree tree;
+    ASSERT_TRUE(flatten(doc.get(), &tree));
+
+    while (tree.next() != android::ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+    }
+
+    EXPECT_EQ(tree.indexOfClass(), 0);
+    EXPECT_EQ(tree.indexOfStyle(), 1);
+}
+
+/*
+ * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+ * namespace.
+ */
+TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+
+    android::ResXMLTree tree;
+    ASSERT_TRUE(flatten(doc.get(), &tree));
+
+    while (tree.next() != android::ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+    }
+
+    const StringPiece16 kPackage = u"package";
+    EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
new file mode 100644
index 0000000..0ccafc2
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "SdkConstants.h"
+#include "ValueVisitor.h"
+
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <cassert>
+
+namespace aapt {
+
+static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) {
+    return lhs.config < config;
+}
+
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+                                     const int sdkVersionToGenerate) {
+    assert(sdkVersionToGenerate > config.sdkVersion);
+    const auto endIter = entry->values.end();
+    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue);
+
+    // The source config came from this list, so it should be here.
+    assert(iter != entry->values.end());
+    ++iter;
+
+    // The next configuration either only varies in sdkVersion, or it is completely different
+    // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
+
+    // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
+    // qualifiers, so we need to iterate through the entire list to be sure there
+    // are no higher sdk level versions of this resource.
+    ConfigDescription tempConfig(config);
+    for (; iter != endIter; ++iter) {
+        tempConfig.sdkVersion = iter->config.sdkVersion;
+        if (tempConfig == iter->config) {
+            // The two configs are the same, check the sdk version.
+            return sdkVersionToGenerate < iter->config.sdkVersion;
+        }
+    }
+
+    // No match was found, so we should generate the versioned resource.
+    return true;
+}
+
+bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        for (auto& type : package->types) {
+            if (type->type != ResourceType::kStyle) {
+                continue;
+            }
+
+            for (auto& entry : type->entries) {
+                for (size_t i = 0; i < entry->values.size(); i++) {
+                    ResourceConfigValue& configValue = entry->values[i];
+                    if (configValue.config.sdkVersion >= SDK_LOLLIPOP_MR1) {
+                        // If this configuration is only used on L-MR1 then we don't need
+                        // to do anything since we use private attributes since that version.
+                        continue;
+                    }
+
+                    if (Style* style = valueCast<Style>(configValue.value.get())) {
+                        Maybe<size_t> minSdkStripped;
+                        std::vector<Style::Entry> stripped;
+
+                        auto iter = style->entries.begin();
+                        while (iter != style->entries.end()) {
+                            assert(iter->key.id && "IDs must be assigned and linked");
+
+                            // Find the SDK level that is higher than the configuration allows.
+                            const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value());
+                            if (sdkLevel > std::max<size_t>(configValue.config.sdkVersion, 1)) {
+                                // Record that we are about to strip this.
+                                stripped.emplace_back(std::move(*iter));
+
+                                // We use the smallest SDK level to generate the new style.
+                                if (minSdkStripped) {
+                                    minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
+                                } else {
+                                    minSdkStripped = sdkLevel;
+                                }
+
+                                // Erase this from this style.
+                                iter = style->entries.erase(iter);
+                                continue;
+                            }
+                            ++iter;
+                        }
+
+                        if (minSdkStripped && !stripped.empty()) {
+                            // We found attributes from a higher SDK level. Check that
+                            // there is no other defined resource for the version we want to
+                            // generate.
+                            if (shouldGenerateVersionedResource(entry.get(), configValue.config,
+                                                                minSdkStripped.value())) {
+                                // Let's create a new Style for this versioned resource.
+                                ConfigDescription newConfig(configValue.config);
+                                newConfig.sdkVersion = minSdkStripped.value();
+
+                                ResourceConfigValue newValue = {
+                                        newConfig,
+                                        configValue.source,
+                                        configValue.comment,
+                                        std::unique_ptr<Value>(configValue.value->clone(
+                                                &table->stringPool))
+                                };
+
+                                Style* newStyle = static_cast<Style*>(newValue.value.get());
+
+                                // Move the previously stripped attributes into this style.
+                                newStyle->entries.insert(newStyle->entries.end(),
+                                                         std::make_move_iterator(stripped.begin()),
+                                                         std::make_move_iterator(stripped.end()));
+
+                                // Insert the new Resource into the correct place.
+                                auto iter = std::lower_bound(entry->values.begin(),
+                                                             entry->values.end(), newConfig,
+                                                             cmpConfigValue);
+                                entry->values.insert(iter, std::move(newValue));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
new file mode 100644
index 0000000..29bcc93
--- /dev/null
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+
+#include "link/Linkers.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(AutoVersionerTest, GenerateVersionedResources) {
+    const ConfigDescription defaultConfig = {};
+    const ConfigDescription landConfig = test::parseConfigOrDie("land");
+    const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
+
+    ResourceEntry entry(u"foo");
+    entry.values.push_back(ResourceConfigValue{ defaultConfig });
+    entry.values.push_back(ResourceConfigValue{ landConfig });
+    entry.values.push_back(ResourceConfigValue{ sw600dpLandConfig });
+
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
+}
+
+TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
+    const ConfigDescription defaultConfig = {};
+    const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13");
+    const ConfigDescription v21Config = test::parseConfigOrDie("v21");
+
+    ResourceEntry entry(u"foo");
+    entry.values.push_back(ResourceConfigValue{ defaultConfig });
+    entry.values.push_back(ResourceConfigValue{ sw600dpV13Config });
+    entry.values.push_back(ResourceConfigValue{ v21Config });
+
+    EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+    EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
+}
+
+TEST(AutoVersionerTest, VersionStylesForTable) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"app", 0x7f)
+            .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v4"),
+                      test::StyleBuilder()
+                            .addItem(u"@android:attr/onClick", ResourceId(0x0101026f),
+                                     util::make_unique<Id>())
+                            .addItem(u"@android:attr/paddingStart", ResourceId(0x010103b3),
+                                     util::make_unique<Id>())
+                            .addItem(u"@android:attr/requiresSmallestWidthDp",
+                                     ResourceId(0x01010364), util::make_unique<Id>())
+                            .addItem(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                     util::make_unique<Id>())
+                            .build())
+            .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v21"),
+                      test::StyleBuilder()
+                            .addItem(u"@android:attr/paddingEnd", ResourceId(0x010103b4),
+                                     util::make_unique<Id>())
+                            .build())
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"app")
+            .setPackageId(0x7f)
+            .build();
+
+    AutoVersioner versioner;
+    ASSERT_TRUE(versioner.consume(context.get(), table.get()));
+
+    Style* style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                                  test::parseConfigOrDie("v4"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 1u);
+    AAPT_ASSERT_TRUE(style->entries.front().key.name);
+    EXPECT_EQ(style->entries.front().key.name.value(),
+              test::parseNameOrDie(u"@android:attr/onClick"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v13"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 2u);
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+              test::parseNameOrDie(u"@android:attr/onClick"));
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v17"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 3u);
+    AAPT_ASSERT_TRUE(style->entries[0].key.name);
+    EXPECT_EQ(style->entries[0].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/onClick"));
+    AAPT_ASSERT_TRUE(style->entries[1].key.name);
+    EXPECT_EQ(style->entries[1].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp"));
+    AAPT_ASSERT_TRUE(style->entries[2].key.name);
+    EXPECT_EQ(style->entries[2].key.name.value(),
+                  test::parseNameOrDie(u"@android:attr/paddingStart"));
+
+    style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo",
+                                           test::parseConfigOrDie("v21"));
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(style->entries.size(), 1u);
+    AAPT_ASSERT_TRUE(style->entries.front().key.name);
+    EXPECT_EQ(style->entries.front().key.name.value(),
+              test::parseNameOrDie(u"@android:attr/paddingEnd"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
new file mode 100644
index 0000000..b84f2e0
--- /dev/null
+++ b/tools/aapt2/link/Link.cpp
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AppInfo.h"
+#include "Debug.h"
+#include "Flags.h"
+#include "JavaClassGenerator.h"
+#include "NameMangler.h"
+#include "ProguardRules.h"
+#include "XmlDom.h"
+
+#include "compile/IdAssigner.h"
+#include "flatten/Archive.h"
+#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
+#include "link/Linkers.h"
+#include "link/TableMerger.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "unflatten/FileExportHeaderReader.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <fstream>
+#include <sys/stat.h>
+#include <utils/FileMap.h>
+#include <vector>
+
+namespace aapt {
+
+struct LinkOptions {
+    std::string outputPath;
+    std::string manifestPath;
+    std::vector<std::string> includePaths;
+    Maybe<std::string> generateJavaClassPath;
+    Maybe<std::string> generateProguardRulesPath;
+    bool noAutoVersion = false;
+    bool staticLib = false;
+    bool verbose = false;
+    bool outputToDirectory = false;
+    Maybe<std::u16string> privateSymbols;
+};
+
+struct LinkContext : public IAaptContext {
+    StdErrDiagnostics mDiagnostics;
+    std::unique_ptr<NameMangler> mNameMangler;
+    std::u16string mCompilationPackage;
+    uint8_t mPackageId;
+    std::unique_ptr<ISymbolTable> mSymbols;
+
+    IDiagnostics* getDiagnostics() override {
+        return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+        return mNameMangler.get();
+    }
+
+    StringPiece16 getCompilationPackage() override {
+        return mCompilationPackage;
+    }
+
+    uint8_t getPackageId() override {
+        return mPackageId;
+    }
+
+    ISymbolTable* getExternalSymbols() override {
+        return mSymbols.get();
+    }
+};
+
+struct LinkCommand {
+    LinkOptions mOptions;
+    LinkContext mContext;
+
+    std::string buildResourceFileName(const ResourceFile& resFile) {
+        std::stringstream out;
+        out << "res/" << resFile.name.type;
+        if (resFile.config != ConfigDescription{}) {
+            out << "-" << resFile.config;
+        }
+        out << "/";
+
+        if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) {
+            out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
+        } else {
+            out << resFile.name.entry;
+        }
+        out << file::getExtension(resFile.source.path);
+        return out.str();
+    }
+
+    /**
+     * Creates a SymbolTable that loads symbols from the various APKs and caches the
+     * results for faster lookup.
+     */
+    std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
+        AssetManagerSymbolTableBuilder builder;
+        for (const std::string& path : mOptions.includePaths) {
+            if (mOptions.verbose) {
+                mContext.getDiagnostics()->note(
+                        DiagMessage(Source{ path }) << "loading include path");
+            }
+
+            std::unique_ptr<android::AssetManager> assetManager =
+                    util::make_unique<android::AssetManager>();
+            int32_t cookie = 0;
+            if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
+                mContext.getDiagnostics()->error(
+                        DiagMessage(Source{ path }) << "failed to load include path");
+                return {};
+            }
+            builder.add(std::move(assetManager));
+        }
+        return builder.build();
+    }
+
+    /**
+     * Loads the resource table (not inside an apk) at the given path.
+     */
+    std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
+        std::string errorStr;
+        Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
+        if (!map) {
+            mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr);
+            return {};
+        }
+
+        std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+        BinaryResourceParser parser(&mContext, table.get(), Source{ input },
+                                    map.value().getDataPtr(), map.value().getDataLength());
+        if (!parser.parse()) {
+            return {};
+        }
+        return table;
+    }
+
+    /**
+     * Inflates an XML file from the source path.
+     */
+    std::unique_ptr<XmlResource> loadXml(const std::string& path) {
+        std::ifstream fin(path, std::ifstream::binary);
+        if (!fin) {
+            mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno));
+            return {};
+        }
+
+        return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path });
+    }
+
+    /**
+     * Inflates a binary XML file from the source path.
+     */
+    std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
+        // Read header for symbol info and export info.
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
+                                              maybeF.value().getDataLength(), &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        std::unique_ptr<XmlResource> xmlRes = xml::inflate(
+                (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
+                maybeF.value().getDataLength() - offset,
+                mContext.getDiagnostics(), Source(path));
+        if (!xmlRes) {
+            return {};
+        }
+        return xmlRes;
+    }
+
+    Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
+        // Read header for symbol info and export info.
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+
+        ResourceFile resFile;
+        ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
+                                                maybeF.value().getDataLength(),
+                                                &resFile, &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return {};
+        }
+        return std::move(resFile);
+    }
+
+    bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
+                           IArchiveWriter* writer) {
+        std::string errorStr;
+        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
+        if (!maybeF) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return false;
+        }
+
+        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
+                                              maybeF.value().getDataLength(),
+                                              &errorStr);
+        if (offset < 0) {
+            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
+                                                 offset, maybeF.value().getDataLength() - offset);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
+            return false;
+        }
+        return true;
+    }
+
+    Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
+        xml::Node* node = xmlRes->root.get();
+
+        // Find the first xml::Element.
+        while (node && !xml::nodeCast<xml::Element>(node)) {
+            node = !node->children.empty() ? node->children.front().get() : nullptr;
+        }
+
+        // Make sure the first element is <manifest> with package attribute.
+        if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) {
+            if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
+                if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
+                    return AppInfo{ packageAttr->value };
+                }
+            }
+        }
+        return {};
+    }
+
+    bool verifyNoExternalPackages(ResourceTable* table) {
+        bool error = false;
+        for (const auto& package : table->packages) {
+            if (mContext.getCompilationPackage() != package->name ||
+                    !package->id || package->id.value() != mContext.getPackageId()) {
+                // We have a package that is not related to the one we're building!
+                for (const auto& type : package->types) {
+                    for (const auto& entry : type->entries) {
+                        for (const auto& configValue : entry->values) {
+                            mContext.getDiagnostics()->error(DiagMessage(configValue.source)
+                                                             << "defined resource '"
+                                                             << ResourceNameRef(package->name,
+                                                                                type->type,
+                                                                                entry->name)
+                                                             << "' for external package '"
+                                                             << package->name << "'");
+                            error = true;
+                        }
+                    }
+                }
+            }
+        }
+        return !error;
+    }
+
+    std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
+        if (mOptions.outputToDirectory) {
+            return createDirectoryArchiveWriter(mOptions.outputPath);
+        } else {
+            return createZipFileArchiveWriter(mOptions.outputPath);
+        }
+    }
+
+    bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
+        BigBuffer buffer(1024);
+        TableFlattenerOptions options = {};
+        options.useExtendedChunks = mOptions.staticLib;
+        TableFlattener flattener(&buffer, options);
+        if (!flattener.consume(&mContext, table)) {
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage() << "failed to write resources.arsc to archive");
+            return false;
+        }
+        return true;
+    }
+
+    bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+                    IArchiveWriter* writer) {
+        BigBuffer buffer(1024);
+        XmlFlattenerOptions options = {};
+        options.keepRawValues = mOptions.staticLib;
+        options.maxSdkLevel = maxSdkLevel;
+        XmlFlattener flattener(&buffer, options);
+        if (!flattener.consume(&mContext, xmlRes)) {
+            return false;
+        }
+
+        ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
+        if (!entry) {
+            mContext.getDiagnostics()->error(
+                    DiagMessage() << "failed to write " << path << " to archive");
+            return false;
+        }
+        return true;
+    }
+
+    bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
+                       const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
+        if (!mOptions.generateJavaClassPath) {
+            return true;
+        }
+
+        std::string outPath = mOptions.generateJavaClassPath.value();
+        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
+        file::mkdirs(outPath);
+        file::appendPath(&outPath, "R.java");
+
+        std::ofstream fout(outPath, std::ofstream::binary);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        JavaClassGenerator generator(table, javaOptions);
+        if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
+            mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
+            return false;
+        }
+        return true;
+    }
+
+    bool writeProguardFile(const proguard::KeepSet& keepSet) {
+        if (!mOptions.generateProguardRulesPath) {
+            return true;
+        }
+
+        std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        proguard::writeKeepSet(&fout, keepSet);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+        return true;
+    }
+
+    int run(const std::vector<std::string>& inputFiles) {
+        // Load the AndroidManifest.xml
+        std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+        if (!manifestXml) {
+            return 1;
+        }
+
+        if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
+            mContext.mCompilationPackage = maybeAppInfo.value().package;
+        } else {
+            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+                                             << "no package specified in <manifest> tag");
+            return 1;
+        }
+
+        if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
+            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+                                             << "invalid package name '"
+                                             << mContext.mCompilationPackage
+                                             << "'");
+            return 1;
+        }
+
+        mContext.mNameMangler = util::make_unique<NameMangler>(
+                NameManglerPolicy{ mContext.mCompilationPackage });
+
+        if (mContext.mCompilationPackage == u"android") {
+            mContext.mPackageId = 0x01;
+        } else {
+            mContext.mPackageId = 0x7f;
+        }
+
+        mContext.mSymbols = createSymbolTableFromIncludePaths();
+        if (!mContext.mSymbols) {
+            return 1;
+        }
+
+        if (mOptions.verbose) {
+            mContext.getDiagnostics()->note(
+                    DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
+                                  << "with package ID " << std::hex << (int) mContext.mPackageId);
+        }
+
+        ResourceTable mergedTable;
+        TableMerger tableMerger(&mContext, &mergedTable);
+
+        struct FilesToProcess {
+            Source source;
+            ResourceFile file;
+        };
+
+        bool error = false;
+        std::queue<FilesToProcess> filesToProcess;
+        for (const std::string& input : inputFiles) {
+            if (util::stringEndsWith<char>(input, ".apk")) {
+                // TODO(adamlesinski): Load resources from a static library APK
+                //                     Merge the table into TableMerger.
+
+            } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+                }
+
+                std::unique_ptr<ResourceTable> table = loadTable(input);
+                if (!table) {
+                    return 1;
+                }
+
+                if (!tableMerger.merge(Source(input), table.get())) {
+                    return 1;
+                }
+
+            } else {
+                // Extract the exported IDs here so we can build the resource table.
+                if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
+                    ResourceFile& f = maybeF.value();
+
+                    if (f.name.package.empty()) {
+                        f.name.package = mContext.getCompilationPackage().toString();
+                    }
+
+                    Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(f.name);
+
+                    // Add this file to the table.
+                    if (!mergedTable.addFileReference(mangledName ? mangledName.value() : f.name,
+                                                      f.config, f.source,
+                                                      util::utf8ToUtf16(buildResourceFileName(f)),
+                                                      mContext.getDiagnostics())) {
+                        error = true;
+                    }
+
+                    // Add the exports of this file to the table.
+                    for (SourcedResourceName& exportedSymbol : f.exportedSymbols) {
+                        if (exportedSymbol.name.package.empty()) {
+                            exportedSymbol.name.package = mContext.getCompilationPackage()
+                                    .toString();
+                        }
+
+                        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
+                                exportedSymbol.name);
+                        if (!mergedTable.addResource(
+                                mangledName ? mangledName.value() : exportedSymbol.name,
+                                {}, {}, f.source.withLine(exportedSymbol.line),
+                                util::make_unique<Id>(), mContext.getDiagnostics())) {
+                            error = true;
+                        }
+                    }
+
+                    filesToProcess.push(FilesToProcess{ Source(input), std::move(f) });
+                } else {
+                    return 1;
+                }
+            }
+        }
+
+        if (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+            return 1;
+        }
+
+        if (!verifyNoExternalPackages(&mergedTable)) {
+            return 1;
+        }
+
+        if (!mOptions.staticLib) {
+            PrivateAttributeMover mover;
+            if (!mover.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(
+                        DiagMessage() << "failed moving private attributes");
+                return 1;
+            }
+        }
+
+        {
+            IdAssigner idAssigner;
+            if (!idAssigner.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
+                return 1;
+            }
+        }
+
+        mContext.mNameMangler = util::make_unique<NameMangler>(
+                NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() });
+        mContext.mSymbols = JoinedSymbolTableBuilder()
+                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable))
+                .addSymbolTable(std::move(mContext.mSymbols))
+                .build();
+
+        {
+            ReferenceLinker linker;
+            if (!linker.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
+                return 1;
+            }
+        }
+
+        proguard::KeepSet proguardKeepSet;
+
+        std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
+        if (!archiveWriter) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
+            return 1;
+        }
+
+        {
+            XmlReferenceLinker manifestLinker;
+            if (manifestLinker.consume(&mContext, manifestXml.get())) {
+
+                if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
+                                                               manifestXml.get(),
+                                                               &proguardKeepSet)) {
+                    error = true;
+                }
+
+                if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
+                                archiveWriter.get())) {
+                    error = true;
+                }
+            } else {
+                error = true;
+            }
+        }
+
+        for (; !filesToProcess.empty(); filesToProcess.pop()) {
+            FilesToProcess& f = filesToProcess.front();
+            if (f.file.name.type != ResourceType::kRaw &&
+                    util::stringEndsWith<char>(f.source.path, ".xml.flat")) {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path);
+                }
+
+                std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path);
+                if (!xmlRes) {
+                    return 1;
+                }
+
+                xmlRes->file = std::move(f.file);
+
+                XmlReferenceLinker xmlLinker;
+                if (xmlLinker.consume(&mContext, xmlRes.get())) {
+                    if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
+                                                        &proguardKeepSet)) {
+                        error = true;
+                    }
+
+                    Maybe<size_t> maxSdkLevel;
+                    if (!mOptions.noAutoVersion) {
+                        maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
+                    }
+
+                    if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel,
+                                    archiveWriter.get())) {
+                        error = true;
+                    }
+
+                    if (!mOptions.noAutoVersion) {
+                        Maybe<ResourceTable::SearchResult> result = mergedTable.findResource(
+                                xmlRes->file.name);
+                        for (int sdkLevel : xmlLinker.getSdkLevels()) {
+                            if (sdkLevel > xmlRes->file.config.sdkVersion &&
+                                    shouldGenerateVersionedResource(result.value().entry,
+                                                                    xmlRes->file.config,
+                                                                    sdkLevel)) {
+                                xmlRes->file.config.sdkVersion = sdkLevel;
+                                if (!mergedTable.addFileReference(xmlRes->file.name,
+                                                                  xmlRes->file.config,
+                                                                  xmlRes->file.source,
+                                                                  util::utf8ToUtf16(
+                                                                     buildResourceFileName(xmlRes->file)),
+                                                             mContext.getDiagnostics())) {
+                                    error = true;
+                                    continue;
+                                }
+
+                                if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file),
+                                                sdkLevel, archiveWriter.get())) {
+                                    error = true;
+                                }
+                            }
+                        }
+                    }
+
+                } else {
+                    error = true;
+                }
+            } else {
+                if (mOptions.verbose) {
+                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path);
+                }
+
+                if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0,
+                                       archiveWriter.get())) {
+                    error = true;
+                }
+            }
+        }
+
+        if (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
+            return 1;
+        }
+
+        if (!mOptions.noAutoVersion) {
+            AutoVersioner versioner;
+            if (!versioner.consume(&mContext, &mergedTable)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
+                return 1;
+            }
+        }
+
+        if (!flattenTable(&mergedTable, archiveWriter.get())) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
+            return 1;
+        }
+
+        if (mOptions.generateJavaClassPath) {
+            JavaClassGeneratorOptions options;
+            if (mOptions.staticLib) {
+                options.useFinal = false;
+            }
+
+            if (mOptions.privateSymbols) {
+                // If we defined a private symbols package, we only emit Public symbols
+                // to the original package, and private and public symbols to the private package.
+
+                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mContext.getCompilationPackage(), options)) {
+                    return 1;
+                }
+
+                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mOptions.privateSymbols.value(), options)) {
+                    return 1;
+                }
+
+            } else {
+                // Emit Everything.
+
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mContext.getCompilationPackage(), options)) {
+                    return 1;
+                }
+            }
+        }
+
+        if (mOptions.generateProguardRulesPath) {
+            if (!writeProguardFile(proguardKeepSet)) {
+                return 1;
+            }
+        }
+
+        if (mOptions.verbose) {
+            Debug::printTable(&mergedTable);
+            for (; !tableMerger.getFileMergeQueue()->empty();
+                    tableMerger.getFileMergeQueue()->pop()) {
+                const FileToMerge& f = tableMerger.getFileMergeQueue()->front();
+                mContext.getDiagnostics()->note(
+                        DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
+                                      << std::hex << (uintptr_t) f.srcTable << std::dec);
+            }
+        }
+
+        return 0;
+    }
+};
+
+int link(const std::vector<StringPiece>& args) {
+    LinkOptions options;
+    Maybe<std::string> privateSymbolsPackage;
+    Flags flags = Flags()
+            .requiredFlag("-o", "Output path", &options.outputPath)
+            .requiredFlag("--manifest", "Path to the Android manifest to build",
+                          &options.manifestPath)
+            .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
+            .optionalFlag("--java", "Directory in which to generate R.java",
+                          &options.generateJavaClassPath)
+            .optionalFlag("--proguard", "Output file for generated Proguard rules",
+                          &options.generateProguardRulesPath)
+            .optionalSwitch("--no-auto-version",
+                            "Disables automatic style and layout SDK versioning",
+                            &options.noAutoVersion)
+            .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
+                            "by -o",
+                            &options.outputToDirectory)
+            .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
+            .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
+                          "private symbols. If not specified, public and private symbols will "
+                          "use the application's package name", &privateSymbolsPackage)
+            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+
+    if (!flags.parse("aapt2 link", args, &std::cerr)) {
+        return 1;
+    }
+
+    if (privateSymbolsPackage) {
+        options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
+    }
+
+    LinkCommand cmd = { options };
+    return cmd.run(flags.getArgs());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
new file mode 100644
index 0000000..2cc8d9f
--- /dev/null
+++ b/tools/aapt2/link/Linkers.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_LINKERS_H
+#define AAPT_LINKER_LINKERS_H
+
+#include "process/IResourceTableConsumer.h"
+
+#include <set>
+
+namespace aapt {
+
+class ResourceTable;
+struct ResourceEntry;
+struct ConfigDescription;
+
+/**
+ * Determines whether a versioned resource should be created. If a versioned resource already
+ * exists, it takes precedence.
+ */
+bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+                                     const int sdkVersionToGenerate);
+
+struct AutoVersioner : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+struct PrivateAttributeMover : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+struct XmlAutoVersioner : public IXmlResourceConsumer {
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+};
+
+struct ReferenceLinker : public IResourceTableConsumer {
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+class XmlReferenceLinker : IXmlResourceConsumer {
+private:
+    std::set<int> mSdkLevelsFound;
+
+public:
+    bool consume(IAaptContext* context, XmlResource* resource) override;
+
+    const std::set<int>& getSdkLevels() const {
+        return mSdkLevelsFound;
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_LINKERS_H */
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
new file mode 100644
index 0000000..5a2f5f0
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+
+#include "link/Linkers.h"
+
+#include <algorithm>
+#include <iterator>
+
+namespace aapt {
+
+template <typename InputContainer, typename OutputIterator, typename Predicate>
+OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result,
+                      Predicate pred) {
+    const auto last = inputContainer.end();
+    auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred);
+    if (newEnd == last) {
+        return result;
+    }
+
+    *result = std::move(*newEnd);
+
+    auto first = newEnd;
+    ++first;
+
+    for (; first != last; ++first) {
+        if (bool(pred(*first))) {
+            // We want to move this guy
+            *result = std::move(*first);
+            ++result;
+        } else {
+            // We want to keep this guy, but we will need to move it up the list to replace
+            // missing items.
+            *newEnd = std::move(*first);
+            ++newEnd;
+        }
+    }
+
+    inputContainer.erase(newEnd, last);
+    return result;
+}
+
+bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        ResourceTableType* type = package->findType(ResourceType::kAttr);
+        if (!type) {
+            continue;
+        }
+
+        if (type->symbolStatus.state != SymbolState::kPublic) {
+            // No public attributes, so we can safely leave these private attributes where they are.
+            return true;
+        }
+
+        ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate);
+        assert(privAttrType->entries.empty());
+
+        moveIf(type->entries, std::back_inserter(privAttrType->entries),
+               [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+                   return entry->symbolStatus.state != SymbolState::kPublic;
+               });
+        break;
+    }
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
new file mode 100644
index 0000000..a2f8d19
--- /dev/null
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/Linkers.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(PrivateAttributeMoverTest, MovePrivateAttributes) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/publicA")
+            .addSimple(u"@android:attr/privateA")
+            .addSimple(u"@android:attr/publicB")
+            .addSimple(u"@android:attr/privateB")
+            .build();
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicA"),
+                                      ResourceId(0x01010000), {}, SymbolState::kPublic,
+                                      context->getDiagnostics()));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicB"),
+                                      ResourceId(0x01010002), {}, SymbolState::kPublic,
+                                      context->getDiagnostics()));
+
+    PrivateAttributeMover mover;
+    ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+    ResourceTablePackage* package = table->findPackage(u"android");
+    ASSERT_NE(package, nullptr);
+
+    ResourceTableType* type = package->findType(ResourceType::kAttr);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+    EXPECT_NE(type->findEntry(u"publicA"), nullptr);
+    EXPECT_NE(type->findEntry(u"publicB"), nullptr);
+
+    type = package->findType(ResourceType::kAttrPrivate);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+    EXPECT_NE(type->findEntry(u"privateA"), nullptr);
+    EXPECT_NE(type->findEntry(u"privateB"), nullptr);
+}
+
+TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:attr/privateA")
+            .addSimple(u"@android:attr/privateB")
+            .build();
+
+    PrivateAttributeMover mover;
+    ASSERT_TRUE(mover.consume(context.get(), table.get()));
+
+    ResourceTablePackage* package = table->findPackage(u"android");
+    ASSERT_NE(package, nullptr);
+
+    ResourceTableType* type = package->findType(ResourceType::kAttr);
+    ASSERT_NE(type, nullptr);
+    ASSERT_EQ(type->entries.size(), 2u);
+
+    type = package->findType(ResourceType::kAttrPrivate);
+    ASSERT_EQ(type, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
new file mode 100644
index 0000000..b4fb996
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Diagnostics.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "ValueVisitor.h"
+
+#include "link/Linkers.h"
+#include "link/ReferenceLinkerVisitor.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <cassert>
+
+namespace aapt {
+
+namespace {
+
+/**
+ * The ReferenceLinkerVisitor will follow all references and make sure they point
+ * to resources that actually exist, either in the local resource table, or as external
+ * symbols. Once the target resource has been found, the ID of the resource will be assigned
+ * to the reference object.
+ *
+ * NOTE: All of the entries in the ResourceTable must be assigned IDs.
+ */
+class StyleAndReferenceLinkerVisitor : public ValueVisitor {
+private:
+    ReferenceLinkerVisitor mReferenceVisitor;
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    IPackageDeclStack* mPackageDecls;
+    StringPool* mStringPool;
+    bool mError = false;
+
+    const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) {
+        assert(reference);
+        assert(reference->name || reference->id);
+
+        if (reference->name) {
+            // Transform the package name if it is an alias.
+            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
+                    reference->name.value(), mContext->getCompilationPackage());
+
+            // Mangle the reference name if it should be mangled.
+            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
+                    realName ? realName.value() : reference->name.value());
+
+            const ISymbolTable::Symbol* s = nullptr;
+            if (mangledName) {
+                s = mSymbols->findByName(mangledName.value());
+            } else if (realName) {
+                s = mSymbols->findByName(realName.value());
+            } else {
+                s = mSymbols->findByName(reference->name.value());
+            }
+
+            if (s && s->attribute) {
+                return s;
+            }
+        }
+
+        if (reference->id) {
+            if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) {
+                if (s->attribute) {
+                    return s;
+                }
+            }
+        }
+        return nullptr;
+    }
+
+    /**
+     * Transform a RawString value into a more specific, appropriate value, based on the
+     * Attribute. If a non RawString value is passed in, this is an identity transform.
+     */
+    std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
+                                                  const Attribute* attr) {
+        if (RawString* rawString = valueCast<RawString>(value.get())) {
+            std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute(
+                    *rawString->value, attr);
+
+            // If we could not parse as any specific type, try a basic STRING.
+            if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+                util::StringBuilder stringBuilder;
+                stringBuilder.append(*rawString->value);
+                if (stringBuilder) {
+                    transformed = util::make_unique<String>(
+                            mStringPool->makeRef(stringBuilder.str()));
+                }
+            }
+
+            if (transformed) {
+                return transformed;
+            }
+        };
+        return value;
+    }
+
+    void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+                                       const Item* value) {
+        *msg << "expected";
+        if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+            *msg << " boolean";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+            *msg << " color";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+            *msg << " dimension";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+            *msg << " enum";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+            *msg << " flags";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+            *msg << " float";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+            *msg << " fraction";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+            *msg << " integer";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+            *msg << " reference";
+        }
+
+        if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+            *msg << " string";
+        }
+
+        *msg << " but got " << *value;
+    }
+
+public:
+    using ValueVisitor::visit;
+
+    StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+                                   StringPool* stringPool, IPackageDeclStack* decl) :
+            mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols),
+            mPackageDecls(decl), mStringPool(stringPool) {
+    }
+
+    void visit(Reference* reference) override {
+        mReferenceVisitor.visit(reference);
+    }
+
+    /**
+     * We visit the Style specially because during this phase, values of attributes are
+     * all RawString values. Now that we are expected to resolve all symbols, we can
+     * lookup the attributes to find out which types are allowed for the attributes' values.
+     */
+    void visit(Style* style) override {
+        if (style->parent) {
+            visit(&style->parent.value());
+        }
+
+        for (Style::Entry& entry : style->entries) {
+            if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) {
+                // Assign our style key the correct ID.
+                entry.key.id = s->id;
+
+                // Try to convert the value to a more specific, typed value based on the
+                // attribute it is set to.
+                entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get());
+
+                // Link/resolve the final value (mostly if it's a reference).
+                entry.value->accept(this);
+
+                // Now verify that the type of this item is compatible with the attribute it
+                // is defined for.
+                android::Res_value val = {};
+                entry.value->flatten(&val);
+
+                // Always allow references.
+                const uint32_t typeMask = s->attribute->typeMask |
+                        android::ResTable_map::TYPE_REFERENCE;
+
+                if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+                    // The actual type of this item is incompatible with the attribute.
+                    DiagMessage msg;
+                    buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
+                    mContext->getDiagnostics()->error(msg);
+                    mError = true;
+                }
+            } else {
+                DiagMessage msg;
+                msg << "style attribute '";
+                if (entry.key.name) {
+                    msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
+                } else {
+                    msg << entry.key.id.value();
+                }
+                msg << "' not found";
+                mContext->getDiagnostics()->error(msg);
+                mError = true;
+            }
+        }
+    }
+
+    inline bool hasError() {
+        return mError || mReferenceVisitor.hasError();
+    }
+};
+
+struct EmptyDeclStack : public IPackageDeclStack {
+    Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                         const StringPiece16& localPackage) const override {
+        if (name.package.empty()) {
+            return ResourceName{ localPackage.toString(), name.type, name.entry };
+        }
+        return {};
+    }
+};
+
+} // namespace
+
+bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) {
+    EmptyDeclStack declStack;
+    bool error = false;
+    for (auto& package : table->packages) {
+        for (auto& type : package->types) {
+            for (auto& entry : type->entries) {
+                // Symbol state information may be lost if there is no value for the resource.
+                if (entry->symbolStatus.state != SymbolState::kUndefined && entry->values.empty()) {
+                    context->getDiagnostics()->error(
+                            DiagMessage(entry->symbolStatus.source)
+                            << "no definition for declared symbol '"
+                            << ResourceNameRef(package->name, type->type, entry->name)
+                            << "'");
+                    error = true;
+                }
+
+                for (auto& configValue : entry->values) {
+                    StyleAndReferenceLinkerVisitor visitor(context,
+                                                           context->getExternalSymbols(),
+                                                           &table->stringPool, &declStack);
+                    configValue.value->accept(&visitor);
+                    if (visitor.hasError()) {
+                        error = true;
+                    }
+                }
+            }
+        }
+    }
+    return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
new file mode 100644
index 0000000..c70531b
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinkerVisitor.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_REFERENCELINKERVISITOR_H
+#define AAPT_LINKER_REFERENCELINKERVISITOR_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * The ReferenceLinkerVisitor will follow all references and make sure they point
+ * to resources that actually exist in the given ISymbolTable.
+ * Once the target resource has been found, the ID of the resource will be assigned
+ * to the reference object.
+ */
+class ReferenceLinkerVisitor : public ValueVisitor {
+    using ValueVisitor::visit;
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    IPackageDeclStack* mPackageDecls;
+    bool mError = false;
+
+public:
+    ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) :
+            mContext(context), mSymbols(symbols), mPackageDecls(decls) {
+    }
+
+    /**
+     * Lookup a reference and ensure it exists, either in our local table, or as an external
+     * symbol. Once found, assign the ID of the target resource to this reference object.
+     */
+    void visit(Reference* reference) override {
+        assert(reference);
+        assert(reference->name || reference->id);
+
+        // We prefer to lookup by name if the name is set. Otherwise it could be
+        // an out-of-date ID.
+        if (reference->name) {
+            // Transform the package name if it is an alias.
+            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
+                    reference->name.value(), mContext->getCompilationPackage());
+
+            // Mangle the reference name if it should be mangled.
+            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
+                    realName ? realName.value() : reference->name.value());
+
+            const ISymbolTable::Symbol* s = nullptr;
+            if (mangledName) {
+                s = mSymbols->findByName(mangledName.value());
+            } else if (realName) {
+                s = mSymbols->findByName(realName.value());
+            } else {
+                s = mSymbols->findByName(reference->name.value());
+            }
+
+            if (s) {
+                reference->id = s->id;
+                return;
+            }
+
+            DiagMessage errorMsg;
+            errorMsg << "reference to " << reference->name.value();
+            if (realName) {
+                errorMsg << " (aka " << realName.value() << ")";
+            }
+            errorMsg << " was not found";
+            mContext->getDiagnostics()->error(errorMsg);
+            mError = true;
+            return;
+        }
+
+        if (!mSymbols->findById(reference->id.value())) {
+            mContext->getDiagnostics()->error(DiagMessage()
+                                              << "reference to " << reference->id.value()
+                                              << " was not found");
+            mError = true;
+        }
+    }
+
+    inline bool hasError() {
+        return mError;
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
new file mode 100644
index 0000000..5e7641a
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/Linkers.h"
+#include "process/SymbolTable.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ReferenceLinkerTest, LinkSimpleReferences) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+                          u"@com.app.test:string/bar")
+
+            // Test use of local reference (w/o package name).
+            .addReference(u"@com.app.test:string/bar", ResourceId(0x7f020001), u"@string/baz")
+
+            .addReference(u"@com.app.test:string/baz", ResourceId(0x7f020002),
+                          u"@android:string/ok")
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(JoinedSymbolTableBuilder()
+                            .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+                            .addSymbolTable(test::StaticSymbolTableBuilder()
+                                    .addSymbol(u"@android:string/ok", ResourceId(0x01040034))
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Reference* ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/foo");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+
+    ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/bar");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002));
+
+    ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/baz");
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x01040034));
+}
+
+TEST(ReferenceLinkerTest, LinkStyleAttributes) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+                    .setParent(u"@android:style/Theme.Material")
+                    .addItem(u"@android:attr/foo", ResourceUtils::tryParseColor(u"#ff00ff"))
+                    .addItem(u"@android:attr/bar", {} /* placeholder */)
+                    .build())
+            .build();
+
+    {
+        // We need to fill in the value for the attribute android:attr/bar after we build the
+        // table, because we need access to the string pool.
+        Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+        ASSERT_NE(style, nullptr);
+        style->entries.back().value = util::make_unique<RawString>(
+                table->stringPool.makeRef(u"one|two"));
+    }
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(test::StaticSymbolTableBuilder()
+                    .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
+                    .addSymbol(u"@android:attr/foo", ResourceId(0x01010001),
+                               test::AttributeBuilder()
+                                    .setTypeMask(android::ResTable_map::TYPE_COLOR)
+                                    .build())
+                    .addSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+                               test::AttributeBuilder()
+                                    .setTypeMask(android::ResTable_map::TYPE_FLAGS)
+                                    .addItem(u"one", 0x01)
+                                    .addItem(u"two", 0x02)
+                                    .build())
+                    .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    AAPT_ASSERT_TRUE(style->parent);
+    AAPT_ASSERT_TRUE(style->parent.value().id);
+    EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000));
+
+    ASSERT_EQ(2u, style->entries.size());
+
+    AAPT_ASSERT_TRUE(style->entries[0].key.id);
+    EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001));
+    ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr);
+
+    AAPT_ASSERT_TRUE(style->entries[1].key.id);
+    EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002));
+    ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr);
+}
+
+TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+            .setSymbolTable(test::StaticSymbolTableBuilder()
+                    .addSymbol(u"@com.app.test:attr/com.android.support$foo",
+                               ResourceId(0x7f010000), test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                    .build())
+            .build();
+
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f020000),
+                      test::StyleBuilder().addItem(u"@com.android.support:attr/foo",
+                                                   ResourceUtils::tryParseColor(u"#ff0000"))
+                                          .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(context.get(), table.get()));
+
+    Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
+    ASSERT_NE(style, nullptr);
+    ASSERT_EQ(1u, style->entries.size());
+    AAPT_ASSERT_TRUE(style->entries.front().key.id);
+    EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
new file mode 100644
index 0000000..db52546
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "link/TableMerger.h"
+#include "util/Util.h"
+
+#include <cassert>
+
+namespace aapt {
+
+TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable) :
+        mContext(context), mMasterTable(outTable) {
+    // Create the desired package that all tables will be merged into.
+    mMasterPackage = mMasterTable->createPackage(
+            mContext->getCompilationPackage(), mContext->getPackageId());
+    assert(mMasterPackage && "package name or ID already taken");
+}
+
+bool TableMerger::merge(const Source& src, ResourceTable* table) {
+    const uint8_t desiredPackageId = mContext->getPackageId();
+
+    bool error = false;
+    for (auto& package : table->packages) {
+        // Warn of packages with an unrelated ID.
+        if (package->id && package->id.value() != 0x0 && package->id.value() != desiredPackageId) {
+            mContext->getDiagnostics()->warn(DiagMessage(src)
+                                             << "ignoring package " << package->name);
+            continue;
+        }
+
+        bool manglePackage = false;
+        if (!package->name.empty() && mContext->getCompilationPackage() != package->name) {
+            manglePackage = true;
+            mMergedPackages.insert(package->name);
+        }
+
+        // Merge here. Once the entries are merged and mangled, any references to
+        // them are still valid. This is because un-mangled references are
+        // mangled, then looked up at resolution time.
+        // Also, when linking, we convert references with no package name to use
+        // the compilation package name.
+        if (!doMerge(src, table, package.get(), manglePackage)) {
+            error = true;
+        }
+    }
+    return !error;
+}
+
+bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
+                          ResourceTablePackage* srcPackage, const bool manglePackage) {
+    bool error = false;
+
+    for (auto& srcType : srcPackage->types) {
+        ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
+        if (srcType->symbolStatus.state == SymbolState::kPublic) {
+            if (dstType->symbolStatus.state == SymbolState::kPublic && dstType->id && srcType->id
+                    && dstType->id.value() == srcType->id.value()) {
+                // Both types are public and have different IDs.
+                mContext->getDiagnostics()->error(DiagMessage(src)
+                                                  << "can not merge type '"
+                                                  << srcType->type
+                                                  << "': conflicting public IDs");
+                error = true;
+                continue;
+            }
+
+            dstType->symbolStatus = std::move(srcType->symbolStatus);
+            dstType->id = srcType->id;
+        }
+
+        for (auto& srcEntry : srcType->entries) {
+            ResourceEntry* dstEntry;
+            if (manglePackage) {
+                dstEntry = dstType->findOrCreateEntry(NameMangler::mangleEntry(
+                        srcPackage->name, srcEntry->name));
+            } else {
+                dstEntry = dstType->findOrCreateEntry(srcEntry->name);
+            }
+
+            if (srcEntry->symbolStatus.state != SymbolState::kUndefined) {
+                if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
+                    if (dstEntry->symbolStatus.state == SymbolState::kPublic &&
+                            dstEntry->id && srcEntry->id &&
+                            dstEntry->id.value() != srcEntry->id.value()) {
+                        // Both entries are public and have different IDs.
+                        mContext->getDiagnostics()->error(DiagMessage(src)
+                                                          << "can not merge entry '"
+                                                          << srcEntry->name
+                                                          << "': conflicting public IDs");
+                        error = true;
+                        continue;
+                    }
+
+                    if (srcEntry->id) {
+                        dstEntry->id = srcEntry->id;
+                    }
+                }
+
+                if (dstEntry->symbolStatus.state != SymbolState::kPublic &&
+                        dstEntry->symbolStatus.state != srcEntry->symbolStatus.state) {
+                    dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
+                }
+            }
+
+            for (ResourceConfigValue& srcValue : srcEntry->values) {
+                auto cmp = [](const ResourceConfigValue& a,
+                              const ConfigDescription& b) -> bool {
+                    return a.config < b;
+                };
+
+                auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
+                                             srcValue.config, cmp);
+
+                if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
+                    const int collisionResult = ResourceTable::resolveValueCollision(
+                            iter->value.get(), srcValue.value.get());
+                    if (collisionResult == 0) {
+                        // Error!
+                        ResourceNameRef resourceName =
+                                { srcPackage->name, srcType->type, srcEntry->name };
+                        mContext->getDiagnostics()->error(DiagMessage(srcValue.source)
+                                                          << "resource '" << resourceName
+                                                          << "' has a conflicting value for "
+                                                          << "configuration ("
+                                                          << srcValue.config << ")");
+                        mContext->getDiagnostics()->note(DiagMessage(iter->source)
+                                                         << "originally defined here");
+                        error = true;
+                        continue;
+                    } else if (collisionResult < 0) {
+                        // Keep our existing value.
+                        continue;
+                    }
+
+                } else {
+                    // Insert a new value.
+                    iter = dstEntry->values.insert(iter,
+                                                   ResourceConfigValue{ srcValue.config });
+                }
+
+                iter->source = std::move(srcValue.source);
+                iter->comment = std::move(srcValue.comment);
+                if (manglePackage) {
+                    iter->value = cloneAndMangle(srcTable, srcPackage->name,
+                                                 srcValue.value.get());
+                } else {
+                    iter->value = clone(srcValue.value.get());
+                }
+            }
+        }
+    }
+    return !error;
+}
+
+std::unique_ptr<Value> TableMerger::cloneAndMangle(ResourceTable* table,
+                                                   const std::u16string& package,
+                                                   Value* value) {
+    if (FileReference* f = valueCast<FileReference>(value)) {
+        // Mangle the path.
+        StringPiece16 prefix, entry, suffix;
+        if (util::extractResFilePathParts(*f->path, &prefix, &entry, &suffix)) {
+            std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
+            std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
+            mFilesToMerge.push(FileToMerge{ table, *f->path, newPath });
+            return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath));
+        }
+    }
+    return clone(value);
+}
+
+std::unique_ptr<Value> TableMerger::clone(Value* value) {
+    return std::unique_ptr<Value>(value->clone(&mMasterTable->stringPool));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
new file mode 100644
index 0000000..157c16e
--- /dev/null
+++ b/tools/aapt2/link/TableMerger.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TABLEMERGER_H
+#define AAPT_TABLEMERGER_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
+
+#include <queue>
+#include <set>
+
+namespace aapt {
+
+struct FileToMerge {
+    ResourceTable* srcTable;
+    std::u16string srcPath;
+    std::u16string dstPath;
+};
+
+/**
+ * TableMerger takes resource tables and merges all packages within the tables that have the same
+ * package ID.
+ *
+ * If a package has a different name, all the entries in that table have their names mangled
+ * to include the package name. This way there are no collisions. In order to do this correctly,
+ * the TableMerger needs to also mangle any FileReference paths. Once these are mangled,
+ * the original source path of the file, along with the new destination path is recorded in the
+ * queue returned from getFileMergeQueue().
+ *
+ * Once the merging is complete, a separate process can go collect the files from the various
+ * source APKs and either copy or process their XML and put them in the correct location in
+ * the final APK.
+ */
+class TableMerger {
+public:
+    TableMerger(IAaptContext* context, ResourceTable* outTable);
+
+    inline std::queue<FileToMerge>* getFileMergeQueue() {
+        return &mFilesToMerge;
+    }
+
+    inline const std::set<std::u16string>& getMergedPackages() const {
+        return mMergedPackages;
+    }
+
+    bool merge(const Source& src, ResourceTable* table);
+
+private:
+    IAaptContext* mContext;
+    ResourceTable* mMasterTable;
+    ResourceTablePackage* mMasterPackage;
+
+    std::set<std::u16string> mMergedPackages;
+    std::queue<FileToMerge> mFilesToMerge;
+
+    bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage,
+                 const bool manglePackage);
+
+    std::unique_ptr<Value> cloneAndMangle(ResourceTable* table, const std::u16string& package,
+                                          Value* value);
+    std::unique_ptr<Value> clone(Value* value);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_TABLEMERGER_H */
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
new file mode 100644
index 0000000..fa7ce86
--- /dev/null
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/TableMerger.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct TableMergerTest : public ::testing::Test {
+    std::unique_ptr<IAaptContext> mContext;
+
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                // We are compiling this package.
+                .setCompilationPackage(u"com.app.a")
+
+                // Merge all packages that have this package ID.
+                .setPackageId(0x7f)
+
+                // Mangle all packages that do not have this package name.
+                .setNameManglerPolicy(NameManglerPolicy{ u"com.app.a", { u"com.app.b" } })
+
+                .build();
+    }
+};
+
+TEST_F(TableMergerTest, SimpleMerge) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.a", 0x7f)
+            .addReference(u"@com.app.a:id/foo", u"@com.app.a:id/bar")
+            .addReference(u"@com.app.a:id/bar", u"@com.app.b:id/foo")
+            .addValue(u"@com.app.a:styleable/view", test::StyleableBuilder()
+                    .addItem(u"@com.app.b:id/foo")
+                    .build())
+            .build();
+
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.b", 0x7f)
+            .addSimple(u"@com.app.b:id/foo")
+            .build();
+
+    ResourceTable finalTable;
+    TableMerger merger(mContext.get(), &finalTable);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableB.get()));
+
+    EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
+
+    // Entries from com.app.a should not be mangled.
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/foo")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/bar")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:styleable/view")));
+
+    // The unmangled name should not be present.
+    AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie(u"@com.app.b:id/foo")));
+
+    // Look for the mangled name.
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo")));
+}
+
+TEST_F(TableMergerTest, MergeFileReferences) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.a", 0x7f)
+            .addFileReference(u"@com.app.a:xml/file", u"res/xml/file.xml")
+            .build();
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.b", 0x7f)
+            .addFileReference(u"@com.app.b:xml/file", u"res/xml/file.xml")
+            .build();
+
+    ResourceTable finalTable;
+    TableMerger merger(mContext.get(), &finalTable);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableB.get()));
+
+    FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
+    ASSERT_NE(f, nullptr);
+    EXPECT_EQ(std::u16string(u"res/xml/file.xml"), *f->path);
+
+    f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file");
+    ASSERT_NE(f, nullptr);
+    EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
+
+    std::queue<FileToMerge>* filesToMerge = merger.getFileMergeQueue();
+    ASSERT_FALSE(filesToMerge->empty());
+
+    FileToMerge& fileToMerge = filesToMerge->front();
+    EXPECT_EQ(fileToMerge.srcTable, tableB.get());
+    EXPECT_EQ(fileToMerge.srcPath, u"res/xml/file.xml");
+    EXPECT_EQ(fileToMerge.dstPath, u"res/xml/com.app.b$file.xml");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
new file mode 100644
index 0000000..147b9bf
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Diagnostics.h"
+#include "ResourceUtils.h"
+#include "SdkConstants.h"
+#include "XmlDom.h"
+
+#include "link/Linkers.h"
+#include "link/ReferenceLinkerVisitor.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+namespace {
+
+class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    std::set<int>* mSdkLevelsFound;
+    ReferenceLinkerVisitor mReferenceLinkerVisitor;
+    bool mError = false;
+
+public:
+    using xml::PackageAwareVisitor::visit;
+
+    XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+                              std::set<int>* sdkLevelsFound) :
+            mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound),
+            mReferenceLinkerVisitor(context, symbols, this) {
+    }
+
+    void visit(xml::Element* el) override {
+        for (xml::Attribute& attr : el->attributes) {
+            Maybe<std::u16string> maybePackage =
+                    util::extractPackageFromNamespace(attr.namespaceUri);
+            if (maybePackage) {
+                // There is a valid package name for this attribute. We will look this up.
+                StringPiece16 package = maybePackage.value();
+                if (package.empty()) {
+                    // Empty package means the 'current' or 'local' package.
+                    package = mContext->getCompilationPackage();
+                }
+
+                attr.compiledAttribute = compileAttribute(
+                        ResourceName{ package.toString(), ResourceType::kAttr, attr.name });
+
+                // Convert the string value into a compiled Value if this is a valid attribute.
+                if (attr.compiledAttribute) {
+                    // Record all SDK levels from which the attributes were defined.
+                    const int sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id);
+                    if (sdkLevel > 1) {
+                        mSdkLevelsFound->insert(sdkLevel);
+                    }
+
+                    const Attribute* attribute = &attr.compiledAttribute.value().attribute;
+                    attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value,
+                                                                              attribute);
+                    if (!attr.compiledValue &&
+                            !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
+                        // We won't be able to encode this as a string.
+                        mContext->getDiagnostics()->error(
+                                DiagMessage() << "'" << attr.value << "' "
+                                              << "is incompatible with attribute "
+                                              << package << ":" << attr.name << " " << *attribute);
+                        mError = true;
+                    }
+                } else {
+                    mContext->getDiagnostics()->error(
+                            DiagMessage() << "attribute '" << package << ":" << attr.name
+                                          << "' was not found");
+                    mError = true;
+
+                }
+            } else {
+                // We still encode references.
+                attr.compiledValue = ResourceUtils::tryParseReference(attr.value);
+            }
+
+            if (attr.compiledValue) {
+                // With a compiledValue, we must resolve the reference and assign it an ID.
+                attr.compiledValue->accept(&mReferenceLinkerVisitor);
+            }
+        }
+
+        // Call the super implementation.
+        xml::PackageAwareVisitor::visit(el);
+    }
+
+    Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) {
+        Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name);
+        if (const ISymbolTable::Symbol* symbol = mSymbols->findByName(
+                mangledName ? mangledName.value() : name)) {
+            if (symbol->attribute) {
+                return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+            }
+        }
+        return {};
+    }
+
+    inline bool hasError() {
+        return mError || mReferenceLinkerVisitor.hasError();
+    }
+};
+
+} // namespace
+
+bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
+    mSdkLevelsFound.clear();
+    XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound);
+    if (resource->root) {
+        resource->root->accept(&visitor);
+        return !visitor.hasError();
+    }
+    return false;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
new file mode 100644
index 0000000..7f91ec3
--- /dev/null
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "link/Linkers.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+class XmlReferenceLinkerTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"com.app.test")
+                .setNameManglerPolicy(
+                        NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
+                .setSymbolTable(test::StaticSymbolTableBuilder()
+                        .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_ENUM |
+                                                     android::ResTable_map::TYPE_DIMENSION)
+                                        .addItem(u"match_parent", 0xffffffff)
+                                        .build())
+                        .addSymbol(u"@android:attr/background", ResourceId(0x01010001),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+                                   test::AttributeBuilder().build())
+                        .addSymbol(u"@android:attr/text", ResourceId(0x01010003),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_STRING)
+                                        .build())
+
+                         // Add one real symbol that was introduces in v21
+                        .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                                   test::AttributeBuilder().build())
+
+                        .addSymbol(u"@android:id/id", ResourceId(0x01030000))
+                        .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000))
+                        .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000))
+                        .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001))
+                        .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
+                                   test::AttributeBuilder()
+                                       .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
+                                   ResourceId(0x7f010001), test::AttributeBuilder()
+                                       .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
+                        .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
+                                   test::AttributeBuilder().build())
+                        .build())
+                .build();
+    }
+
+protected:
+    std::unique_ptr<IAaptContext> mContext;
+};
+
+static xml::Element* getRootElement(XmlResource* doc) {
+    xml::Node* node = doc->root.get();
+    while (xml::nodeCast<xml::Namespace>(node)) {
+        if (node->children.empty()) {
+            return nullptr;
+        }
+        node = node->children.front().get();
+    }
+
+    if (xml::Element* el = xml::nodeCast<xml::Element>(node)) {
+        return el;
+    }
+    return nullptr;
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:background="@color/green"
+              android:text="hello"
+              class="hello" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+                                                    u"layout_width");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010000));
+    ASSERT_NE(xmlAttr->compiledValue, nullptr);
+    ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"background");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010001));
+    ASSERT_NE(xmlAttr->compiledValue, nullptr);
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@color/green")); // Make sure the name
+                                                                         // didn't change.
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
+
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"text");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    ASSERT_FALSE(xmlAttr->compiledValue);   // Strings don't get compiled for memory sake.
+
+    xmlAttr = viewEl->findAttribute(u"", u"class");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute);
+    ASSERT_EQ(xmlAttr->compiledValue, nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:colorAccent="#ffffff" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+    EXPECT_TRUE(linker.getSdkLevels().count(21) == 1);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
+                  support:colorAccent="#ff0000" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(
+            u"http://schemas.android.com/apk/res/com.android.support", u"colorAccent");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010001));
+    ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:app="http://schemas.android.com/apk/res-auto"
+                  app:colorAccent="@app:color/red" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
+                                                    u"colorAccent");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010000));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->name);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:app="http://schemas.android.com/apk/res/android"
+                  app:attr="@app:id/id">
+              <View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
+                    app:attr="@app:id/id"/>
+            </View>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "android" (0x01).
+    xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
+                                                    u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010002));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x01030000));
+
+    ASSERT_FALSE(viewEl->getChildElements().empty());
+    viewEl = viewEl->getChildElements().front();
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+    xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002));
+    ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
+    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+            <View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
+                  android:attr="@id/id"/>)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+
+    xml::Element* viewEl = getRootElement(doc.get());
+    ASSERT_NE(viewEl, nullptr);
+
+    // All attributes and references in this element should be referring to "com.app.test" (0x7f).
+    xml::Attribute* xmlAttr = viewEl->findAttribute(
+            u"http://schemas.android.com/apk/res/com.app.test", u"attr");
+    ASSERT_NE(xmlAttr, nullptr);
+    AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+    EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002));
+    Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+    ASSERT_NE(ref, nullptr);
+    AAPT_ASSERT_TRUE(ref->id);
+    EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot
deleted file mode 100644
index 4741952..0000000
--- a/tools/aapt2/process.dot
+++ /dev/null
@@ -1,108 +0,0 @@
-digraph aapt {
-    out_package [label="out/default/package.apk"];
-    out_fr_package [label="out/fr/package.apk"];
-    out_table_aligned [label="out/default/resources-aligned.arsc"];
-    out_table_fr_aligned [label="out/fr/resources-aligned.arsc"];
-    out_res_layout_main_xml [label="out/res/layout/main.xml"];
-    out_res_layout_v21_main_xml [color=red,label="out/res/layout-v21/main.xml"];
-    out_res_layout_fr_main_xml [label="out/res/layout-fr/main.xml"];
-    out_res_layout_fr_v21_main_xml [color=red,label="out/res/layout-fr-v21/main.xml"];
-    out_table [label="out/default/resources.arsc"];
-    out_fr_table [label="out/fr/resources.arsc"];
-    out_values_table [label="out/values/resources.arsc"];
-    out_layout_table [label="out/layout/resources.arsc"];
-    out_values_fr_table [label="out/values-fr/resources.arsc"];
-    out_layout_fr_table [label="out/layout-fr/resources.arsc"];
-    res_values_strings_xml [label="res/values/strings.xml"];
-    res_values_attrs_xml [label="res/values/attrs.xml"];
-    res_layout_main_xml [label="res/layout/main.xml"];
-    res_layout_fr_main_xml [label="res/layout-fr/main.xml"];
-    res_values_fr_strings_xml [label="res/values-fr/strings.xml"];
-
-    lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green];
-    lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green];
-    lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green];
-    lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green];
-    lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green];
-    out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"];
-
-    out_package -> package_default;
-    out_fr_package -> package_fr;
-
-    package_default [shape=box,label="Assemble",color=blue];
-    package_default -> out_table_aligned;
-    package_default -> out_res_layout_main_xml;
-    package_default -> out_res_layout_v21_main_xml [color=red];
-    package_default -> out_res_layout_lib_main_xml;
-
-    package_fr [shape=box,label="Assemble",color=blue];
-    package_fr -> out_table_fr_aligned;
-    package_fr -> out_res_layout_fr_main_xml;
-    package_fr -> out_res_layout_fr_v21_main_xml [color=red];
-
-    out_table_aligned -> align_tables;
-    out_table_fr_aligned -> align_tables;
-
-    align_tables [shape=box,label="Align",color=blue];
-    align_tables -> out_table;
-    align_tables -> out_fr_table;
-
-    out_table -> link_tables;
-
-    link_tables [shape=box,label="Link",color=blue];
-    link_tables -> out_values_table;
-    link_tables -> out_layout_table;
-    link_tables -> lib_apk_resources_arsc;
-
-    out_values_table -> compile_values;
-
-    compile_values [shape=box,label="Collect",color=blue];
-    compile_values -> res_values_strings_xml;
-    compile_values -> res_values_attrs_xml;
-
-    out_layout_table -> collect_xml;
-
-    collect_xml [shape=box,label="Collect",color=blue];
-    collect_xml -> res_layout_main_xml;
-
-    out_fr_table -> link_fr_tables;
-
-    link_fr_tables [shape=box,label="Link",color=blue];
-    link_fr_tables -> out_values_fr_table;
-    link_fr_tables -> out_layout_fr_table;
-    link_fr_tables -> lib_apk_resources_arsc;
-
-    out_values_fr_table -> compile_values_fr;
-
-    compile_values_fr [shape=box,label="Collect",color=blue];
-    compile_values_fr -> res_values_fr_strings_xml;
-
-    out_layout_fr_table -> collect_xml_fr;
-
-    collect_xml_fr [shape=box,label="Collect",color=blue];
-    collect_xml_fr -> res_layout_fr_main_xml;
-
-    compile_res_layout_main_xml [shape=box,label="Compile",color=blue];
-
-    out_res_layout_main_xml -> compile_res_layout_main_xml;
-
-    out_res_layout_v21_main_xml -> compile_res_layout_main_xml [color=red];
-
-    compile_res_layout_main_xml -> res_layout_main_xml;
-    compile_res_layout_main_xml -> out_table_aligned;
-
-    compile_res_layout_fr_main_xml [shape=box,label="Compile",color=blue];
-
-    out_res_layout_fr_main_xml -> compile_res_layout_fr_main_xml;
-
-    out_res_layout_fr_v21_main_xml -> compile_res_layout_fr_main_xml [color=red];
-
-    compile_res_layout_fr_main_xml -> res_layout_fr_main_xml;
-    compile_res_layout_fr_main_xml -> out_table_fr_aligned;
-
-    out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml;
-
-    compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue];
-    compile_res_layout_lib_main_xml -> out_table_aligned;
-    compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml;
-}
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
new file mode 100644
index 0000000..24ad05d
--- /dev/null
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROCESS_IRESOURCETABLECONSUMER_H
+#define AAPT_PROCESS_IRESOURCETABLECONSUMER_H
+
+#include "Diagnostics.h"
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "Source.h"
+
+#include <iostream>
+#include <list>
+#include <sstream>
+
+namespace aapt {
+
+class ResourceTable;
+struct ISymbolTable;
+
+struct IAaptContext {
+    virtual ~IAaptContext() = default;
+
+    virtual ISymbolTable* getExternalSymbols() = 0;
+    virtual IDiagnostics* getDiagnostics() = 0;
+    virtual StringPiece16 getCompilationPackage() = 0;
+    virtual uint8_t getPackageId() = 0;
+    virtual NameMangler* getNameMangler() = 0;
+};
+
+struct IResourceTableConsumer {
+    virtual ~IResourceTableConsumer() = default;
+
+    virtual bool consume(IAaptContext* context, ResourceTable* table) = 0;
+};
+
+namespace xml {
+struct Node;
+}
+
+struct XmlResource {
+    ResourceFile file;
+    std::unique_ptr<xml::Node> root;
+};
+
+struct IXmlResourceConsumer {
+    virtual ~IXmlResourceConsumer() = default;
+
+    virtual bool consume(IAaptContext* context, XmlResource* resource) = 0;
+};
+
+struct IPackageDeclStack {
+    virtual ~IPackageDeclStack() = default;
+
+    virtual Maybe<ResourceName> transformPackage(const ResourceName& name,
+                                                 const StringPiece16& localPackage) const = 0;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
new file mode 100644
index 0000000..c96b080
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ConfigDescription.h"
+#include "Resource.h"
+#include "util/Util.h"
+
+#include "process/SymbolTable.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& name) {
+    if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
+        return s.get();
+    }
+
+    Maybe<ResourceTable::SearchResult> result = mTable->findResource(name);
+    if (!result) {
+        if (name.type == ResourceType::kAttr) {
+            // Recurse and try looking up a private attribute.
+            return findByName(ResourceName{ name.package, ResourceType::kAttrPrivate, name.entry });
+        }
+        return {};
+    }
+
+    ResourceTable::SearchResult sr = result.value();
+
+    // If no ID exists, we treat the symbol as missing. SymbolTables are used to
+    // find symbols to link.
+    if (!sr.package->id || !sr.type->id || !sr.entry->id) {
+        return {};
+    }
+
+    std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
+    symbol->id = ResourceId{
+            sr.package->id.value(), sr.type->id.value(), sr.entry->id.value() };
+
+    if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
+        auto lt = [](ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
+            return lhs.config < rhs;
+        };
+
+        const ConfigDescription kDefaultConfig;
+        auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
+                                     kDefaultConfig, lt);
+
+        if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
+            // This resource has an Attribute.
+            symbol->attribute = util::make_unique<Attribute>(
+                    *static_cast<Attribute*>(iter->value.get()));
+        }
+    }
+
+    if (name.type == ResourceType::kAttrPrivate) {
+        // Masquerade this entry as kAttr.
+        mCache.put(ResourceName{ name.package, ResourceType::kAttr, name.entry }, symbol);
+    } else {
+        mCache.put(name, symbol);
+    }
+    return symbol.get();
+}
+
+
+static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table,
+                                                             ResourceId id) {
+    android::Res_value val = {};
+    ssize_t block = table.getResource(id.id, &val, true);
+    if (block >= 0) {
+        std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>();
+        s->id = id;
+        return s;
+    }
+
+    // Try as a bag.
+    const android::ResTable::bag_entry* entry;
+    ssize_t count = table.lockBag(id.id, &entry);
+    if (count < 0) {
+        table.unlockBag(entry);
+        return nullptr;
+    }
+
+    // We found a resource.
+    std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>();
+    s->id = id;
+
+    // Check to see if it is an attribute.
+    for (size_t i = 0; i < (size_t) count; i++) {
+        if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
+            s->attribute = util::make_unique<Attribute>(false);
+            s->attribute->typeMask = entry[i].map.value.data;
+            break;
+        }
+    }
+
+    if (s->attribute) {
+        for (size_t i = 0; i < (size_t) count; i++) {
+            if (!Res_INTERNALID(entry[i].map.name.ident)) {
+                android::ResTable::resource_name entryName;
+                if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) {
+                    table.unlockBag(entry);
+                    return nullptr;
+                }
+
+                const ResourceType* parsedType = parseResourceType(
+                        StringPiece16(entryName.type, entryName.typeLen));
+                if (!parsedType) {
+                    table.unlockBag(entry);
+                    return nullptr;
+                }
+
+                Attribute::Symbol symbol;
+                symbol.symbol.name = ResourceNameRef(
+                        StringPiece16(entryName.package, entryName.packageLen),
+                        *parsedType,
+                        StringPiece16(entryName.name, entryName.nameLen)).toResourceName();
+                symbol.symbol.id = ResourceId(entry[i].map.name.ident);
+                symbol.value = entry[i].map.value.data;
+                s->attribute->symbols.push_back(std::move(symbol));
+            }
+        }
+    }
+    table.unlockBag(entry);
+    return s;
+}
+
+const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findByName(
+        const ResourceName& name) {
+    if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
+        return s.get();
+    }
+
+    for (const auto& asset : mAssets) {
+        const android::ResTable& table = asset->getResources(false);
+        StringPiece16 typeStr = toString(name.type);
+        ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(),
+                                                   typeStr.data(), typeStr.size(),
+                                                   name.package.data(), name.package.size());
+        if (!resId.isValid()) {
+            continue;
+        }
+
+        std::shared_ptr<Symbol> s = lookupIdInTable(table, resId);
+        if (s) {
+            mCache.put(name, s);
+            return s.get();
+        }
+    }
+    return nullptr;
+}
+
+const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById(
+        ResourceId id) {
+    if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
+        return s.get();
+    }
+
+    for (const auto& asset : mAssets) {
+        const android::ResTable& table = asset->getResources(false);
+
+        std::shared_ptr<Symbol> s = lookupIdInTable(table, id);
+        if (s) {
+            mIdCache.put(id, s);
+            return s.get();
+        }
+    }
+    return nullptr;
+}
+
+const ISymbolTable::Symbol* JoinedSymbolTableBuilder::JoinedSymbolTable::findByName(
+        const ResourceName& name) {
+    for (auto& symbolTable : mSymbolTables) {
+        if (const Symbol* s = symbolTable->findByName(name)) {
+            return s;
+        }
+    }
+    return {};
+}
+
+const ISymbolTable::Symbol* JoinedSymbolTableBuilder::JoinedSymbolTable::findById(ResourceId id) {
+    for (auto& symbolTable : mSymbolTables) {
+        if (const Symbol* s = symbolTable->findById(id)) {
+            return s;
+        }
+    }
+    return {};
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
new file mode 100644
index 0000000..22096ed
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROCESS_SYMBOLTABLE_H
+#define AAPT_PROCESS_SYMBOLTABLE_H
+
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+
+#include <utils/JenkinsHash.h>
+#include <utils/LruCache.h>
+
+#include <androidfw/AssetManager.h>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace aapt {
+
+struct ISymbolTable {
+    virtual ~ISymbolTable() = default;
+
+    struct Symbol {
+        ResourceId id;
+        std::unique_ptr<Attribute> attribute;
+        bool isPublic;
+    };
+
+    /**
+     * Never hold on to the result between calls to findByName or findById. The results
+     * are typically stored in a cache which may evict entries.
+     */
+    virtual const Symbol* findByName(const ResourceName& name) = 0;
+    virtual const Symbol* findById(ResourceId id) = 0;
+};
+
+inline android::hash_t hash_type(const ResourceName& name) {
+    std::hash<std::u16string> strHash;
+    android::hash_t hash = 0;
+    hash = android::JenkinsHashMix(hash, strHash(name.package));
+    hash = android::JenkinsHashMix(hash, (uint32_t) name.type);
+    hash = android::JenkinsHashMix(hash, strHash(name.entry));
+    return hash;
+}
+
+inline android::hash_t hash_type(const ResourceId& id) {
+    return android::hash_type(id.id);
+}
+
+/**
+ * Presents a ResourceTable as an ISymbolTable, caching results.
+ * Instances of this class must outlive the encompassed ResourceTable.
+ * Since symbols are cached, the ResourceTable should not change during the
+ * lifetime of this SymbolTableWrapper.
+ *
+ * If a resource in the ResourceTable does not have a ResourceID assigned to it,
+ * it is ignored.
+ *
+ * Lookups by ID are ignored.
+ */
+class SymbolTableWrapper : public ISymbolTable {
+private:
+    ResourceTable* mTable;
+
+    // We use shared_ptr because unique_ptr is not supported and
+    // we need automatic deletion.
+    android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache;
+
+public:
+    SymbolTableWrapper(ResourceTable* table) : mTable(table), mCache(200) {
+    }
+
+    const Symbol* findByName(const ResourceName& name) override;
+
+    // Unsupported, all queries to ResourceTable should be done by name.
+    const Symbol* findById(ResourceId id) override {
+        return {};
+    }
+};
+
+class AssetManagerSymbolTableBuilder {
+private:
+    struct AssetManagerSymbolTable : public ISymbolTable {
+        std::vector<std::unique_ptr<android::AssetManager>> mAssets;
+
+        // We use shared_ptr because unique_ptr is not supported and
+        // we need automatic deletion.
+        android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache;
+        android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache;
+
+        AssetManagerSymbolTable() : mCache(200), mIdCache(200) {
+        }
+
+        const Symbol* findByName(const ResourceName& name) override;
+        const Symbol* findById(ResourceId id) override;
+    };
+
+    std::unique_ptr<AssetManagerSymbolTable> mSymbolTable =
+            util::make_unique<AssetManagerSymbolTable>();
+
+public:
+    AssetManagerSymbolTableBuilder& add(std::unique_ptr<android::AssetManager> assetManager) {
+        mSymbolTable->mAssets.push_back(std::move(assetManager));
+        return *this;
+    }
+
+    std::unique_ptr<ISymbolTable> build() {
+        return std::move(mSymbolTable);
+    }
+};
+
+class JoinedSymbolTableBuilder {
+private:
+    struct JoinedSymbolTable : public ISymbolTable {
+        std::vector<std::unique_ptr<ISymbolTable>> mSymbolTables;
+
+        const Symbol* findByName(const ResourceName& name) override;
+        const Symbol* findById(ResourceId id) override;
+    };
+
+    std::unique_ptr<JoinedSymbolTable> mSymbolTable = util::make_unique<JoinedSymbolTable>();
+
+public:
+    JoinedSymbolTableBuilder& addSymbolTable(std::unique_ptr<ISymbolTable> table) {
+        mSymbolTable->mSymbolTables.push_back(std::move(table));
+        return *this;
+    }
+
+    std::unique_ptr<ISymbolTable> build() {
+        return std::move(mSymbolTable);
+    }
+};
+
+} // namespace aapt
+
+#endif /* AAPT_PROCESS_SYMBOLTABLE_H */
diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp
new file mode 100644
index 0000000..1dc3b4f
--- /dev/null
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "process/SymbolTable.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(SymbolTableWrapperTest, FindSymbolsWithIds) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple(u"@android:id/foo", ResourceId(0x01020000))
+            .addSimple(u"@android:id/bar")
+            .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+                      test::AttributeBuilder().build())
+            .build();
+
+    SymbolTableWrapper symbolTable(table.get());
+    EXPECT_NE(symbolTable.findByName(test::parseNameOrDie(u"@android:id/foo")), nullptr);
+    EXPECT_EQ(symbolTable.findByName(test::parseNameOrDie(u"@android:id/bar")), nullptr);
+
+    const ISymbolTable::Symbol* s = symbolTable.findByName(
+            test::parseNameOrDie(u"@android:attr/foo"));
+    ASSERT_NE(s, nullptr);
+    EXPECT_NE(s->attribute, nullptr);
+}
+
+TEST(SymbolTableWrapperTest, FindPrivateAttrSymbol) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addValue(u"@android:^attr-private/foo", ResourceId(0x01010000),
+                      test::AttributeBuilder().build())
+            .build();
+
+    SymbolTableWrapper symbolTable(table.get());
+    const ISymbolTable::Symbol* s = symbolTable.findByName(
+                test::parseNameOrDie(u"@android:attr/foo"));
+    ASSERT_NE(s, nullptr);
+    EXPECT_NE(s->attribute, nullptr);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
new file mode 100644
index 0000000..0383c44
--- /dev/null
+++ b/tools/aapt2/test/Builders.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TEST_BUILDERS_H
+#define AAPT_TEST_BUILDERS_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "util/Util.h"
+#include "XmlDom.h"
+
+#include "test/Common.h"
+
+#include <memory>
+
+namespace aapt {
+namespace test {
+
+class ResourceTableBuilder {
+private:
+    DummyDiagnosticsImpl mDiagnostics;
+    std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>();
+
+public:
+    ResourceTableBuilder() = default;
+
+    ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) {
+        ResourceTablePackage* package = mTable->createPackage(packageName, id);
+        assert(package);
+        return *this;
+    }
+
+    ResourceTableBuilder& addSimple(const StringPiece16& name, ResourceId id = {}) {
+        return addValue(name, id, util::make_unique<Id>());
+    }
+
+    ResourceTableBuilder& addReference(const StringPiece16& name, const StringPiece16& ref) {
+        return addReference(name, {}, ref);
+    }
+
+    ResourceTableBuilder& addReference(const StringPiece16& name, ResourceId id,
+                                       const StringPiece16& ref) {
+        return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
+    }
+
+    ResourceTableBuilder& addString(const StringPiece16& name, const StringPiece16& str) {
+        return addString(name, {}, str);
+    }
+
+    ResourceTableBuilder& addString(const StringPiece16& name, ResourceId id,
+                                    const StringPiece16& str) {
+        return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
+    }
+
+    ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path) {
+        return addFileReference(name, {}, path);
+    }
+
+    ResourceTableBuilder& addFileReference(const StringPiece16& name, ResourceId id,
+                                           const StringPiece16& path) {
+        return addValue(name, id,
+                        util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
+    }
+
+
+    ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) {
+        return addValue(name, {}, std::move(value));
+    }
+
+    ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
+                                       std::unique_ptr<Value> value) {
+        return addValue(name, id, {}, std::move(value));
+    }
+
+    ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
+                                   const ConfigDescription& config, std::unique_ptr<Value> value) {
+        ResourceName resName = parseNameOrDie(name);
+        bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value),
+                                                      &mDiagnostics);
+        assert(result);
+        return *this;
+    }
+
+    std::unique_ptr<ResourceTable> build() {
+        return std::move(mTable);
+    }
+};
+
+inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref,
+                                                 Maybe<ResourceId> id = {}) {
+    std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref));
+    reference->id = id;
+    return reference;
+}
+
+class AttributeBuilder {
+private:
+    std::unique_ptr<Attribute> mAttr;
+
+public:
+    AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) {
+        mAttr->typeMask = android::ResTable_map::TYPE_ANY;
+    }
+
+    AttributeBuilder& setTypeMask(uint32_t typeMask) {
+        mAttr->typeMask = typeMask;
+        return *this;
+    }
+
+    AttributeBuilder& addItem(const StringPiece16& name, uint32_t value) {
+        mAttr->symbols.push_back(Attribute::Symbol{
+                Reference(ResourceName{ {}, ResourceType::kId, name.toString()}),
+                value});
+        return *this;
+    }
+
+    std::unique_ptr<Attribute> build() {
+        return std::move(mAttr);
+    }
+};
+
+class StyleBuilder {
+private:
+    std::unique_ptr<Style> mStyle = util::make_unique<Style>();
+
+public:
+    StyleBuilder& setParent(const StringPiece16& str) {
+        mStyle->parent = Reference(parseNameOrDie(str));
+        return *this;
+    }
+
+    StyleBuilder& addItem(const StringPiece16& str, std::unique_ptr<Item> value) {
+        mStyle->entries.push_back(Style::Entry{ Reference(parseNameOrDie(str)), std::move(value) });
+        return *this;
+    }
+
+    StyleBuilder& addItem(const StringPiece16& str, ResourceId id, std::unique_ptr<Item> value) {
+        addItem(str, std::move(value));
+        mStyle->entries.back().key.id = id;
+        return *this;
+    }
+
+    std::unique_ptr<Style> build() {
+        return std::move(mStyle);
+    }
+};
+
+class StyleableBuilder {
+private:
+    std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>();
+
+public:
+    StyleableBuilder& addItem(const StringPiece16& str, Maybe<ResourceId> id = {}) {
+        mStyleable->entries.push_back(Reference(parseNameOrDie(str)));
+        mStyleable->entries.back().id = id;
+        return *this;
+    }
+
+    std::unique_ptr<Styleable> build() {
+        return std::move(mStyleable);
+    }
+};
+
+inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) {
+    std::stringstream in;
+    in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
+    StdErrDiagnostics diag;
+    std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, {});
+    assert(doc);
+    return doc;
+}
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_BUILDERS_H */
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
new file mode 100644
index 0000000..6fdaebb
--- /dev/null
+++ b/tools/aapt2/test/Common.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TEST_COMMON_H
+#define AAPT_TEST_COMMON_H
+
+#include "ConfigDescription.h"
+#include "Debug.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <gtest/gtest.h>
+#include <iostream>
+
+//
+// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to fail to compile.
+//
+#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v))
+#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v))
+#define AAPT_EXPECT_TRUE(v) EXPECT_TRUE(bool(v))
+#define AAPT_EXPECT_FALSE(v) EXPECT_FALSE(bool(v))
+
+namespace aapt {
+namespace test {
+
+struct DummyDiagnosticsImpl : public IDiagnostics {
+    void error(const DiagMessage& message) override {
+        DiagMessageActual actual = message.build();
+        std::cerr << actual.source << ": error: " << actual.message << "." << std::endl;
+    }
+    void warn(const DiagMessage& message) override {
+        DiagMessageActual actual = message.build();
+        std::cerr << actual.source << ": warn: " << actual.message << "." << std::endl;
+    }
+    void note(const DiagMessage& message) override {}
+};
+
+inline ResourceName parseNameOrDie(const StringPiece16& str) {
+    ResourceNameRef ref;
+    bool result = ResourceUtils::tryParseReference(str, &ref);
+    assert(result && "invalid resource name");
+    return ref.toResourceName();
+}
+
+inline ConfigDescription parseConfigOrDie(const StringPiece& str) {
+    ConfigDescription config;
+    bool result = ConfigDescription::parse(str, &config);
+    assert(result && "invalid configuration");
+    return config;
+}
+
+template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName,
+                                           const ConfigDescription& config) {
+    Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName));
+    if (result) {
+        ResourceEntry* entry = result.value().entry;
+        auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
+                                     [](const ResourceConfigValue& a, const ConfigDescription& b)
+                                             -> bool {
+                                         return a.config < b;
+                                     });
+        if (iter != entry->values.end() && iter->config == config) {
+            return valueCast<T>(iter->value.get());
+        }
+    }
+    return nullptr;
+}
+
+template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) {
+    return getValueForConfig<T>(table, resName, {});
+}
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_COMMON_H */
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
new file mode 100644
index 0000000..4fa4918
--- /dev/null
+++ b/tools/aapt2/test/Context.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_TEST_CONTEXT_H
+#define AAPT_TEST_CONTEXT_H
+
+#include "NameMangler.h"
+#include "util/Util.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "test/Common.h"
+
+#include <cassert>
+#include <list>
+
+namespace aapt {
+namespace test {
+
+class Context : public IAaptContext {
+private:
+    friend class ContextBuilder;
+
+    Context() = default;
+
+    Maybe<std::u16string> mCompilationPackage;
+    Maybe<uint8_t> mPackageId;
+    std::unique_ptr<IDiagnostics> mDiagnostics = util::make_unique<StdErrDiagnostics>();
+    std::unique_ptr<ISymbolTable> mSymbols;
+    std::unique_ptr<NameMangler> mNameMangler;
+
+public:
+    ISymbolTable* getExternalSymbols() override {
+        assert(mSymbols && "test symbols not set");
+        return mSymbols.get();
+    }
+
+    void setSymbolTable(std::unique_ptr<ISymbolTable> symbols) {
+        mSymbols = std::move(symbols);
+    }
+
+    IDiagnostics* getDiagnostics() override {
+        assert(mDiagnostics && "test diagnostics not set");
+        return mDiagnostics.get();
+    }
+
+    StringPiece16 getCompilationPackage() override {
+        assert(mCompilationPackage && "package name not set");
+        return mCompilationPackage.value();
+    }
+
+    uint8_t getPackageId() override {
+        assert(mPackageId && "package ID not set");
+        return mPackageId.value();
+    }
+
+    NameMangler* getNameMangler() override {
+        assert(mNameMangler && "test name mangler not set");
+        return mNameMangler.get();
+    }
+};
+
+class ContextBuilder {
+private:
+    std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context());
+
+public:
+    ContextBuilder& setCompilationPackage(const StringPiece16& package) {
+        mContext->mCompilationPackage = package.toString();
+        return *this;
+    }
+
+    ContextBuilder& setPackageId(uint8_t id) {
+        mContext->mPackageId = id;
+        return *this;
+    }
+
+    ContextBuilder& setSymbolTable(std::unique_ptr<ISymbolTable> symbols) {
+        mContext->mSymbols = std::move(symbols);
+        return *this;
+    }
+
+    ContextBuilder& setDiagnostics(std::unique_ptr<IDiagnostics> diag) {
+        mContext->mDiagnostics = std::move(diag);
+        return *this;
+    }
+
+    ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) {
+        mContext->mNameMangler = util::make_unique<NameMangler>(policy);
+        return *this;
+    }
+
+    std::unique_ptr<Context> build() {
+        return std::move(mContext);
+    }
+};
+
+class StaticSymbolTableBuilder {
+private:
+    struct SymbolTable : public ISymbolTable {
+        std::list<std::unique_ptr<Symbol>> mSymbols;
+        std::map<ResourceName, Symbol*> mNameMap;
+        std::map<ResourceId, Symbol*> mIdMap;
+
+        const Symbol* findByName(const ResourceName& name) override {
+            auto iter = mNameMap.find(name);
+            if (iter != mNameMap.end()) {
+                return iter->second;
+            }
+            return nullptr;
+        }
+
+        const Symbol* findById(ResourceId id) override {
+            auto iter = mIdMap.find(id);
+            if (iter != mIdMap.end()) {
+                return iter->second;
+            }
+            return nullptr;
+        }
+    };
+
+    std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>();
+
+public:
+    StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id,
+                                  std::unique_ptr<Attribute> attr = {}) {
+        std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
+                id, std::move(attr));
+        mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
+        mSymbolTable->mIdMap[id] = symbol.get();
+        mSymbolTable->mSymbols.push_back(std::move(symbol));
+        return *this;
+    }
+
+    std::unique_ptr<ISymbolTable> build() {
+        return std::move(mSymbolTable);
+    }
+};
+
+} // namespace test
+} // namespace aapt
+
+#endif /* AAPT_TEST_CONTEXT_H */
diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt
deleted file mode 100644
index acc8bfb..0000000
--- a/tools/aapt2/todo.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-XML Files
-X Collect declared IDs
-X Build StringPool
-X Flatten
-
-Resource Table Operations
-X Build Resource Table (with StringPool) from XML.
-X Modify Resource Table.
-X - Copy and transform resources.
-X   - Pre-17/21 attr correction.
-X Perform analysis of types.
-X Flatten.
-X Assign resource IDs.
-X Assign public resource IDs.
-X Merge resource tables
-- Assign private attributes to different typespace.
-- Align resource tables
-
-Splits
-- Collect all resources (ids from layouts).
-- Generate resource table from base resources.
-- Generate resource table from individual resources of the required type.
-- Align resource tables (same type/name = same ID).
-
-Fat Apk
-X Collect all resources (ids from layouts).
-X Generate resource tables for all configurations.
-- Align individual resource tables.
-- Merge resource tables.
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
new file mode 100644
index 0000000..c7a715e
--- /dev/null
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -0,0 +1,838 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "ValueVisitor.h"
+
+#include "flatten/ResourceTypeExtensions.h"
+#include "unflatten/BinaryResourceParser.h"
+#include "unflatten/ResChunkPullParser.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
+#include <base/macros.h>
+
+#include <map>
+#include <string>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Visitor that converts a reference's resource ID to a resource name,
+ * given a mapping from resource ID to resource name.
+ */
+class ReferenceIdToNameVisitor : public ValueVisitor {
+private:
+    const std::map<ResourceId, ResourceName>* mMapping;
+
+public:
+    using ValueVisitor::visit;
+
+    ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) :
+            mMapping(mapping) {
+        assert(mMapping);
+    }
+
+    void visit(Reference* reference) override {
+        if (!reference->id || !reference->id.value().isValid()) {
+            return;
+        }
+
+        ResourceId id = reference->id.value();
+        auto cacheIter = mMapping->find(id);
+        if (cacheIter != mMapping->end()) {
+            reference->name = cacheIter->second;
+            reference->id = {};
+        }
+    }
+};
+
+BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+                                           const Source& source, const void* data, size_t len) :
+        mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) {
+}
+
+bool BinaryResourceParser::parse() {
+    ResChunkPullParser parser(mData, mDataLen);
+
+    bool error = false;
+    while(ResChunkPullParser::isGoodEvent(parser.next())) {
+        if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
+            mContext->getDiagnostics()->warn(DiagMessage(mSource)
+                                             << "unknown chunk of type '"
+                                             << (int) parser.getChunk()->type << "'");
+            continue;
+        }
+
+        if (!parseTable(parser.getChunk())) {
+            error = true;
+        }
+    }
+
+    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt resource table: "
+                                          << parser.getLastError());
+        return false;
+    }
+    return !error;
+}
+
+bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
+    if (!mSymbolEntries || mSymbolEntryCount == 0) {
+        return false;
+    }
+
+    if ((uintptr_t) data < (uintptr_t) mData) {
+        return false;
+    }
+
+    // We only support 32 bit offsets right now.
+    const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData;
+    if (offset > std::numeric_limits<uint32_t>::max()) {
+        return false;
+    }
+
+    for (size_t i = 0; i < mSymbolEntryCount; i++) {
+        if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) {
+            // This offset is a symbol!
+            const StringPiece16 str = util::getString(
+                    mSymbolPool, util::deviceToHost32(mSymbolEntries[i].stringIndex));
+
+            StringPiece16 typeStr;
+            ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
+                                               &outSymbol->entry);
+            const ResourceType* type = parseResourceType(typeStr);
+            if (!type) {
+                return false;
+            }
+
+            outSymbol->type = *type;
+
+            // Since we scan the symbol table in order, we can start looking for the
+            // next symbol from this point.
+            mSymbolEntryCount -= i + 1;
+            mSymbolEntries += i + 1;
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * Parses the SymbolTable_header, which is present on non-final resource tables
+ * after the compile phase.
+ *
+ * | SymbolTable_header |
+ * |--------------------|
+ * |SymbolTable_entry 0 |
+ * |SymbolTable_entry 1 |
+ * | ...                |
+ * |SymbolTable_entry n |
+ * |--------------------|
+ *
+ */
+bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
+    const SymbolTable_header* header = convertTo<SymbolTable_header>(chunk);
+    if (!header) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt SymbolTable_header");
+        return false;
+    }
+
+    const uint32_t entrySizeBytes =
+            util::deviceToHost32(header->count) * sizeof(SymbolTable_entry);
+    if (entrySizeBytes > getChunkDataLen(&header->header)) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "SymbolTable_header data section too long");
+        return false;
+    }
+
+    mSymbolEntries = (const SymbolTable_entry*) getChunkData(&header->header);
+    mSymbolEntryCount = util::deviceToHost32(header->count);
+
+    // Skip over the symbol entries and parse the StringPool chunk that should be next.
+    ResChunkPullParser parser(getChunkData(&header->header) + entrySizeBytes,
+                              getChunkDataLen(&header->header) - entrySizeBytes);
+    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "failed to parse chunk in SymbolTable: "
+                                          << parser.getLastError());
+        return false;
+    }
+
+    const ResChunk_header* nextChunk = parser.getChunk();
+    if (util::deviceToHost16(nextChunk->type) != android::RES_STRING_POOL_TYPE) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "expected string pool in SymbolTable but got "
+                                          << "chunk of type "
+                                          << (int) util::deviceToHost16(nextChunk->type));
+        return false;
+    }
+
+    if (mSymbolPool.setTo(nextChunk, util::deviceToHost32(nextChunk->size)) != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt string pool in SymbolTable: "
+                                          << mSymbolPool.getError());
+        return false;
+    }
+    return true;
+}
+
+/**
+ * Parses the resource table, which contains all the packages, types, and entries.
+ */
+bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
+    const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
+    if (!tableHeader) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource) << "corrupt ResTable_header chunk");
+        return false;
+    }
+
+    ResChunkPullParser parser(getChunkData(&tableHeader->header),
+                              getChunkDataLen(&tableHeader->header));
+    while (ResChunkPullParser::isGoodEvent(parser.next())) {
+        switch (util::deviceToHost16(parser.getChunk()->type)) {
+        case android::RES_STRING_POOL_TYPE:
+            if (mValuePool.getError() == NO_INIT) {
+                status_t err = mValuePool.setTo(parser.getChunk(),
+                                                util::deviceToHost32(parser.getChunk()->size));
+                if (err != NO_ERROR) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "corrupt string pool in ResTable: "
+                                                      << mValuePool.getError());
+                    return false;
+                }
+
+                // Reserve some space for the strings we are going to add.
+                mTable->stringPool.hintWillAdd(mValuePool.size(), mValuePool.styleCount());
+            } else {
+                mContext->getDiagnostics()->warn(DiagMessage(mSource)
+                                                 << "unexpected string pool in ResTable");
+            }
+            break;
+
+        case RES_TABLE_SYMBOL_TABLE_TYPE:
+            if (!parseSymbolTable(parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        case RES_TABLE_SOURCE_POOL_TYPE: {
+            status_t err = mSourcePool.setTo(getChunkData(parser.getChunk()),
+                                             getChunkDataLen(parser.getChunk()));
+            if (err != NO_ERROR) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "corrupt source string pool in ResTable: "
+                                                  << mSourcePool.getError());
+                return false;
+            }
+            break;
+        }
+
+        case android::RES_TABLE_PACKAGE_TYPE:
+            if (!parsePackage(parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        default:
+            mContext->getDiagnostics()
+                    ->warn(DiagMessage(mSource)
+                           << "unexpected chunk type "
+                           << (int) util::deviceToHost16(parser.getChunk()->type));
+            break;
+        }
+    }
+
+    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt resource table: " << parser.getLastError());
+        return false;
+    }
+    return true;
+}
+
+
+bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
+    const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
+    if (!packageHeader) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_package chunk");
+        return false;
+    }
+
+    uint32_t packageId = util::deviceToHost32(packageHeader->id);
+    if (packageId > std::numeric_limits<uint8_t>::max()) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "package ID is too big (" << packageId << ")");
+        return false;
+    }
+
+    // Extract the package name.
+    size_t len = strnlen16((const char16_t*) packageHeader->name, arraysize(packageHeader->name));
+    std::u16string packageName;
+    packageName.resize(len);
+    for (size_t i = 0; i < len; i++) {
+        packageName[i] = util::deviceToHost16(packageHeader->name[i]);
+    }
+
+    ResourceTablePackage* package = mTable->createPackage(packageName, (uint8_t) packageId);
+    if (!package) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "incompatible package '" << packageName
+                                          << "' with ID " << packageId);
+        return false;
+    }
+
+    ResChunkPullParser parser(getChunkData(&packageHeader->header),
+                              getChunkDataLen(&packageHeader->header));
+    while (ResChunkPullParser::isGoodEvent(parser.next())) {
+        switch (util::deviceToHost16(parser.getChunk()->type)) {
+        case android::RES_STRING_POOL_TYPE:
+            if (mTypePool.getError() == NO_INIT) {
+                status_t err = mTypePool.setTo(parser.getChunk(),
+                                               util::deviceToHost32(parser.getChunk()->size));
+                if (err != NO_ERROR) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "corrupt type string pool in "
+                                                      << "ResTable_package: "
+                                                      << mTypePool.getError());
+                    return false;
+                }
+            } else if (mKeyPool.getError() == NO_INIT) {
+                status_t err = mKeyPool.setTo(parser.getChunk(),
+                                              util::deviceToHost32(parser.getChunk()->size));
+                if (err != NO_ERROR) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "corrupt key string pool in "
+                                                      << "ResTable_package: "
+                                                      << mKeyPool.getError());
+                    return false;
+                }
+            } else {
+                mContext->getDiagnostics()->warn(DiagMessage(mSource) << "unexpected string pool");
+            }
+            break;
+
+        case android::RES_TABLE_TYPE_SPEC_TYPE:
+            if (!parseTypeSpec(parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        case android::RES_TABLE_TYPE_TYPE:
+            if (!parseType(package, parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        case RES_TABLE_PUBLIC_TYPE:
+            if (!parsePublic(package, parser.getChunk())) {
+                return false;
+            }
+            break;
+
+        default:
+            mContext->getDiagnostics()
+                    ->warn(DiagMessage(mSource)
+                           << "unexpected chunk type "
+                           << (int) util::deviceToHost16(parser.getChunk()->type));
+            break;
+        }
+    }
+
+    if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_package: "
+                                          << parser.getLastError());
+        return false;
+    }
+
+    // Now go through the table and change local resource ID references to
+    // symbolic references.
+    ReferenceIdToNameVisitor visitor(&mIdIndex);
+    for (auto& package : mTable->packages) {
+        for (auto& type : package->types) {
+            for (auto& entry : type->entries) {
+                for (auto& configValue : entry->values) {
+                    configValue.value->accept(&visitor);
+                }
+            }
+        }
+    }
+    return true;
+}
+
+bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package,
+                                       const ResChunk_header* chunk) {
+    const Public_header* header = convertTo<Public_header>(chunk);
+    if (!header) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt Public_header chunk");
+        return false;
+    }
+
+    if (header->typeId == 0) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "invalid type ID "
+                                          << (int) header->typeId);
+        return false;
+    }
+
+    StringPiece16 typeStr16 = util::getString(mTypePool, header->typeId - 1);
+    const ResourceType* parsedType = parseResourceType(typeStr16);
+    if (!parsedType) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "invalid type '" << typeStr16 << "'");
+        return false;
+    }
+
+    const uintptr_t chunkEnd = (uintptr_t) chunk + util::deviceToHost32(chunk->size);
+    const Public_entry* entry = (const Public_entry*) getChunkData(&header->header);
+    for (uint32_t i = 0; i < util::deviceToHost32(header->count); i++) {
+        if ((uintptr_t) entry + sizeof(*entry) > chunkEnd) {
+            mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                              << "Public_entry data section is too long");
+            return false;
+        }
+
+        const ResourceId resId(package->id.value(), header->typeId,
+                               util::deviceToHost16(entry->entryId));
+
+        const ResourceName name(package->name, *parsedType,
+                                util::getString(mKeyPool, entry->key.index).toString());
+
+        Source source;
+        if (mSourcePool.getError() == NO_ERROR) {
+            source.path = util::utf16ToUtf8(util::getString(
+                    mSourcePool, util::deviceToHost32(entry->source.index)));
+            source.line = util::deviceToHost32(entry->sourceLine);
+        }
+
+        SymbolState state = SymbolState::kUndefined;
+        switch (util::deviceToHost16(entry->state)) {
+        case Public_entry::kPrivate:
+            state = SymbolState::kPrivate;
+            break;
+
+        case Public_entry::kPublic:
+            state = SymbolState::kPublic;
+            break;
+        }
+
+        if (!mTable->setSymbolStateAllowMangled(name, resId, source, state,
+                                                mContext->getDiagnostics())) {
+            return false;
+        }
+
+        // Add this resource name->id mapping to the index so
+        // that we can resolve all ID references to name references.
+        auto cacheIter = mIdIndex.find(resId);
+        if (cacheIter == mIdIndex.end()) {
+            mIdIndex.insert({ resId, name });
+        }
+
+        entry++;
+    }
+    return true;
+}
+
+bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
+    if (mTypePool.getError() != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "missing type string pool");
+        return false;
+    }
+
+    const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
+    if (!typeSpec) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_typeSpec chunk");
+        return false;
+    }
+
+    if (typeSpec->id == 0) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "ResTable_typeSpec has invalid id: " << typeSpec->id);
+        return false;
+    }
+    return true;
+}
+
+bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
+                                     const ResChunk_header* chunk) {
+    if (mTypePool.getError() != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "missing type string pool");
+        return false;
+    }
+
+    if (mKeyPool.getError() != NO_ERROR) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "missing key string pool");
+        return false;
+    }
+
+    const ResTable_type* type = convertTo<ResTable_type>(chunk);
+    if (!type) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "corrupt ResTable_type chunk");
+        return false;
+    }
+
+    if (type->id == 0) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "ResTable_type has invalid id: " << (int) type->id);
+        return false;
+    }
+
+    ConfigDescription config;
+    config.copyFromDtoH(type->config);
+
+    StringPiece16 typeStr16 = util::getString(mTypePool, type->id - 1);
+
+    const ResourceType* parsedType = parseResourceType(typeStr16);
+    if (!parsedType) {
+        mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                          << "invalid type name '" << typeStr16
+                                          << "' for type with ID " << (int) type->id);
+        return false;
+    }
+
+    TypeVariant tv(type);
+    for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
+        const ResTable_entry* entry = *it;
+        if (!entry) {
+            continue;
+        }
+
+        const ResourceName name(package->name, *parsedType,
+                                util::getString(mKeyPool,
+                                                util::deviceToHost32(entry->key.index)).toString());
+
+        const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
+
+        std::unique_ptr<Value> resourceValue;
+        const ResTable_entry_source* sourceBlock = nullptr;
+
+        if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
+            const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
+            if (util::deviceToHost32(mapEntry->size) - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
+                const uint8_t* data = (const uint8_t*) mapEntry;
+                data += util::deviceToHost32(mapEntry->size) - sizeof(*sourceBlock);
+                sourceBlock = (const ResTable_entry_source*) data;
+            }
+
+            // TODO(adamlesinski): Check that the entry count is valid.
+            resourceValue = parseMapEntry(name, config, mapEntry);
+        } else {
+            if (util::deviceToHost32(entry->size) - sizeof(*entry) == sizeof(*sourceBlock)) {
+                const uint8_t* data = (const uint8_t*) entry;
+                data += util::deviceToHost32(entry->size) - sizeof(*sourceBlock);
+                sourceBlock = (const ResTable_entry_source*) data;
+            }
+
+            const Res_value* value = (const Res_value*)(
+                    (const uint8_t*) entry + util::deviceToHost32(entry->size));
+            resourceValue = parseValue(name, config, value, entry->flags);
+        }
+
+        assert(resourceValue && "failed to interpret valid resource");
+
+        Source source = mSource;
+        if (sourceBlock) {
+            size_t len;
+            const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->pathIndex),
+                                                    &len);
+            if (str) {
+                source.path.assign(str, len);
+            }
+            source.line = util::deviceToHost32(sourceBlock->line);
+        }
+
+        if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue),
+                                             mContext->getDiagnostics())) {
+            return false;
+        }
+
+        if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+            if (!mTable->setSymbolStateAllowMangled(name, resId, mSource.withLine(0),
+                                                    SymbolState::kPublic,
+                                                    mContext->getDiagnostics())) {
+                return false;
+            }
+        }
+
+        // Add this resource name->id mapping to the index so
+        // that we can resolve all ID references to name references.
+        auto cacheIter = mIdIndex.find(resId);
+        if (cacheIter == mIdIndex.end()) {
+            mIdIndex.insert({ resId, name });
+        }
+    }
+    return true;
+}
+
+std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
+                                                       const ConfigDescription& config,
+                                                       const Res_value* value,
+                                                       uint16_t flags) {
+    if (name.type == ResourceType::kId) {
+        return util::make_unique<Id>();
+    }
+
+    const uint32_t data = util::deviceToHost32(value->data);
+
+    if (value->dataType == Res_value::TYPE_STRING) {
+        StringPiece16 str = util::getString(mValuePool, data);
+
+        const ResStringPool_span* spans = mValuePool.styleAt(data);
+
+        // Check if the string has a valid style associated with it.
+        if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
+            StyleString styleStr = { str.toString() };
+            while (spans->name.index != ResStringPool_span::END) {
+                styleStr.spans.push_back(Span{
+                        util::getString(mValuePool, spans->name.index).toString(),
+                        spans->firstChar,
+                        spans->lastChar
+                });
+                spans++;
+            }
+            return util::make_unique<StyledString>(mTable->stringPool.makeRef(
+                    styleStr, StringPool::Context{1, config}));
+        } else {
+            if (name.type != ResourceType::kString &&
+                    util::stringStartsWith<char16_t>(str, u"res/")) {
+                // This must be a FileReference.
+                return util::make_unique<FileReference>(mTable->stringPool.makeRef(
+                            str, StringPool::Context{ 0, config }));
+            }
+
+            // There are no styles associated with this string, so treat it as
+            // a simple string.
+            return util::make_unique<String>(mTable->stringPool.makeRef(
+                    str, StringPool::Context{1, config}));
+        }
+    }
+
+    if (value->dataType == Res_value::TYPE_REFERENCE ||
+            value->dataType == Res_value::TYPE_ATTRIBUTE) {
+        const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
+                    Reference::Type::kResource : Reference::Type::kAttribute;
+
+        if (data != 0) {
+            // This is a normal reference.
+            return util::make_unique<Reference>(data, type);
+        }
+
+        // This reference has an invalid ID. Check if it is an unresolved symbol.
+        ResourceNameRef symbol;
+        if (getSymbol(&value->data, &symbol)) {
+            return util::make_unique<Reference>(symbol, type);
+        }
+
+        // This is not an unresolved symbol, so it must be the magic @null reference.
+        Res_value nullType = {};
+        nullType.dataType = Res_value::TYPE_REFERENCE;
+        return util::make_unique<BinaryPrimitive>(nullType);
+    }
+
+    if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
+        return util::make_unique<RawString>(mTable->stringPool.makeRef(
+                util::getString(mValuePool, data), StringPool::Context{ 1, config }));
+    }
+
+    // Treat this as a raw binary primitive.
+    return util::make_unique<BinaryPrimitive>(*value);
+}
+
+std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
+                                                           const ConfigDescription& config,
+                                                           const ResTable_map_entry* map) {
+    switch (name.type) {
+        case ResourceType::kStyle:
+            return parseStyle(name, config, map);
+        case ResourceType::kAttrPrivate:
+            // fallthrough
+        case ResourceType::kAttr:
+            return parseAttr(name, config, map);
+        case ResourceType::kIntegerArray:
+            // fallthrough
+        case ResourceType::kArray:
+            return parseArray(name, config, map);
+        case ResourceType::kStyleable:
+            return parseStyleable(name, config, map);
+        case ResourceType::kPlurals:
+            return parsePlural(name, config, map);
+        default:
+            assert(false && "unknown map type");
+            break;
+    }
+    return {};
+}
+
+std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
+                                                        const ConfigDescription& config,
+                                                        const ResTable_map_entry* map) {
+    std::unique_ptr<Style> style = util::make_unique<Style>();
+    if (util::deviceToHost32(map->parent.ident) == 0) {
+        // The parent is either not set or it is an unresolved symbol.
+        // Check to see if it is a symbol.
+        ResourceNameRef symbol;
+        if (getSymbol(&map->parent.ident, &symbol)) {
+            style->parent = Reference(symbol.toResourceName());
+        }
+    } else {
+         // The parent is a regular reference to a resource.
+        style->parent = Reference(util::deviceToHost32(map->parent.ident));
+    }
+
+    for (const ResTable_map& mapEntry : map) {
+        style->entries.emplace_back();
+        Style::Entry& styleEntry = style->entries.back();
+
+        if (util::deviceToHost32(mapEntry.name.ident) == 0) {
+            // The map entry's key (attribute) is not set. This must be
+            // a symbol reference, so resolve it.
+            ResourceNameRef symbol;
+            bool result = getSymbol(&mapEntry.name.ident, &symbol);
+            assert(result);
+            styleEntry.key.name = symbol.toResourceName();
+        } else {
+            // The map entry's key (attribute) is a regular reference.
+            styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
+        }
+
+        // Parse the attribute's value.
+        styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
+        assert(styleEntry.value);
+    }
+    return style;
+}
+
+std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
+                                                           const ConfigDescription& config,
+                                                           const ResTable_map_entry* map) {
+    const bool isWeak = (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
+    std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+
+    // First we must discover what type of attribute this is. Find the type mask.
+    auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
+        return util::deviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE;
+    });
+
+    if (typeMaskIter != end(map)) {
+        attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
+    }
+
+    if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+        for (const ResTable_map& mapEntry : map) {
+            if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+                continue;
+            }
+
+            Attribute::Symbol symbol;
+            symbol.value = util::deviceToHost32(mapEntry.value.data);
+            if (util::deviceToHost32(mapEntry.name.ident) == 0) {
+                // The map entry's key (id) is not set. This must be
+                // a symbol reference, so resolve it.
+                ResourceNameRef symbolName;
+                bool result = getSymbol(&mapEntry.name.ident, &symbolName);
+                assert(result);
+                symbol.symbol.name = symbolName.toResourceName();
+            } else {
+                // The map entry's key (id) is a regular reference.
+                symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
+            }
+
+            attr->symbols.push_back(std::move(symbol));
+        }
+    }
+
+    // TODO(adamlesinski): Find min, max, i80n, etc attributes.
+    return attr;
+}
+
+std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
+                                                        const ConfigDescription& config,
+                                                        const ResTable_map_entry* map) {
+    std::unique_ptr<Array> array = util::make_unique<Array>();
+    for (const ResTable_map& mapEntry : map) {
+        array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
+    }
+    return array;
+}
+
+std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
+                                                                const ConfigDescription& config,
+                                                                const ResTable_map_entry* map) {
+    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+    for (const ResTable_map& mapEntry : map) {
+        if (util::deviceToHost32(mapEntry.name.ident) == 0) {
+            // The map entry's key (attribute) is not set. This must be
+            // a symbol reference, so resolve it.
+            ResourceNameRef symbol;
+            bool result = getSymbol(&mapEntry.name.ident, &symbol);
+            assert(result);
+            styleable->entries.emplace_back(symbol);
+        } else {
+            // The map entry's key (attribute) is a regular reference.
+            styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
+        }
+    }
+    return styleable;
+}
+
+std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
+                                                          const ConfigDescription& config,
+                                                          const ResTable_map_entry* map) {
+    std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+    for (const ResTable_map& mapEntry : map) {
+        std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+
+        switch (util::deviceToHost32(mapEntry.name.ident)) {
+            case android::ResTable_map::ATTR_ZERO:
+                plural->values[Plural::Zero] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_ONE:
+                plural->values[Plural::One] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_TWO:
+                plural->values[Plural::Two] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_FEW:
+                plural->values[Plural::Few] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_MANY:
+                plural->values[Plural::Many] = std::move(item);
+                break;
+            case android::ResTable_map::ATTR_OTHER:
+                plural->values[Plural::Other] = std::move(item);
+                break;
+        }
+    }
+    return plural;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
similarity index 82%
rename from tools/aapt2/BinaryResourceParser.h
rename to tools/aapt2/unflatten/BinaryResourceParser.h
index 3aab301..4dbef5d 100644
--- a/tools/aapt2/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -17,11 +17,13 @@
 #ifndef AAPT_BINARY_RESOURCE_PARSER_H
 #define AAPT_BINARY_RESOURCE_PARSER_H
 
-#include "Resolver.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "Source.h"
 
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
 #include <androidfw/ResourceTypes.h>
 #include <string>
 
@@ -42,11 +44,8 @@
      * Creates a parser, which will read `len` bytes from `data`, and
      * add any resources parsed to `table`. `source` is for logging purposes.
      */
-    BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
-                         const std::shared_ptr<IResolver>& resolver,
-                         const Source& source,
-                         const std::u16string& defaultPackage,
-                         const void* data, size_t len);
+    BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+                         const void* data, size_t dataLen);
 
     BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
 
@@ -62,14 +61,10 @@
 
     bool parseTable(const android::ResChunk_header* chunk);
     bool parseSymbolTable(const android::ResChunk_header* chunk);
-
-    // Looks up the resource ID in the reference and converts it to a name if available.
-    bool idToName(Reference* reference);
-
     bool parsePackage(const android::ResChunk_header* chunk);
-    bool parsePublic(const android::ResChunk_header* chunk);
+    bool parsePublic(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
     bool parseTypeSpec(const android::ResChunk_header* chunk);
-    bool parseType(const android::ResChunk_header* chunk);
+    bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
 
     std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
             const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
@@ -92,15 +87,11 @@
     std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
             const ConfigDescription& config, const android::ResTable_map_entry* map);
 
-    std::shared_ptr<ResourceTable> mTable;
-
-    std::shared_ptr<IResolver> mResolver;
+    IAaptContext* mContext;
+    ResourceTable* mTable;
 
     const Source mSource;
 
-    // The package name of the resource table.
-    std::u16string mDefaultPackage;
-
     const void* mData;
     const size_t mDataLen;
 
@@ -146,13 +137,11 @@
  */
 
 inline const ResTable_map* begin(const ResTable_map_entry* map) {
-    return reinterpret_cast<const ResTable_map*>(
-            reinterpret_cast<const uint8_t*>(map) + map->size);
+    return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(map->size));
 }
 
 inline const ResTable_map* end(const ResTable_map_entry* map) {
-    return reinterpret_cast<const ResTable_map*>(
-            reinterpret_cast<const uint8_t*>(map) + map->size) + map->count;
+    return begin(map) + aapt::util::deviceToHost32(map->count);
 }
 
 } // namespace android
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader.h b/tools/aapt2/unflatten/FileExportHeaderReader.h
new file mode 100644
index 0000000..e552ea1
--- /dev/null
+++ b/tools/aapt2/unflatten/FileExportHeaderReader.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H
+#define AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H
+
+#include "ResChunkPullParser.h"
+#include "Resource.h"
+#include "ResourceUtils.h"
+
+#include "flatten/ResourceTypeExtensions.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+static ssize_t parseFileExportHeaderImpl(const void* data, const size_t len,
+                                         const FileExport_header** outFileExport,
+                                         const ExportedSymbol** outExportedSymbolIndices,
+                                         android::ResStringPool* outStringPool,
+                                         std::string* outError) {
+    ResChunkPullParser parser(data, len);
+    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
+        if (outError) *outError = parser.getLastError();
+        return -1;
+    }
+
+    if (util::deviceToHost16(parser.getChunk()->type) != RES_FILE_EXPORT_TYPE) {
+        if (outError) *outError = "no FileExport_header found";
+        return -1;
+    }
+
+    const FileExport_header* fileExport = convertTo<FileExport_header>(parser.getChunk());
+    if (!fileExport) {
+        if (outError) *outError = "corrupt FileExport_header";
+        return -1;
+    }
+
+    if (memcmp(fileExport->magic, "AAPT", sizeof(fileExport->magic)) != 0) {
+        if (outError) *outError = "invalid magic value";
+        return -1;
+    }
+
+    const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
+
+    // Verify that we have enough space for all those symbols.
+    size_t dataLen = getChunkDataLen(&fileExport->header);
+    if (exportedSymbolCount > dataLen / sizeof(ExportedSymbol)) {
+        if (outError) *outError = "too many symbols";
+        return -1;
+    }
+
+    const size_t symbolIndicesSize = exportedSymbolCount * sizeof(ExportedSymbol);
+
+    const void* strPoolData = getChunkData(&fileExport->header) + symbolIndicesSize;
+    const size_t strPoolDataLen = dataLen - symbolIndicesSize;
+    if (outStringPool->setTo(strPoolData, strPoolDataLen, false) != android::NO_ERROR) {
+        if (outError) *outError = "corrupt string pool";
+        return -1;
+    }
+
+    *outFileExport = fileExport;
+    *outExportedSymbolIndices = (const ExportedSymbol*) getChunkData(
+            &fileExport->header);
+    return util::deviceToHost16(fileExport->header.headerSize) + symbolIndicesSize +
+            outStringPool->bytes();
+}
+
+static ssize_t getWrappedDataOffset(const void* data, size_t len, std::string* outError) {
+    const FileExport_header* header = nullptr;
+    const ExportedSymbol* entries = nullptr;
+    android::ResStringPool pool;
+    return parseFileExportHeaderImpl(data, len, &header, &entries, &pool, outError);
+}
+
+/**
+ * Reads the FileExport_header and populates outRes with the values in that header.
+ */
+static ssize_t unwrapFileExportHeader(const void* data, size_t len, ResourceFile* outRes,
+                                      std::string* outError) {
+
+    const FileExport_header* fileExport = nullptr;
+    const ExportedSymbol* entries = nullptr;
+    android::ResStringPool symbolPool;
+    const ssize_t offset = parseFileExportHeaderImpl(data, len, &fileExport, &entries, &symbolPool,
+                                                     outError);
+    if (offset < 0) {
+        return offset;
+    }
+
+    const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
+    outRes->exportedSymbols.clear();
+    outRes->exportedSymbols.reserve(exportedSymbolCount);
+
+    for (size_t i = 0; i < exportedSymbolCount; i++) {
+        const StringPiece16 str = util::getString(symbolPool,
+                                                  util::deviceToHost32(entries[i].name.index));
+        StringPiece16 packageStr, typeStr, entryStr;
+        ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
+        const ResourceType* resType = parseResourceType(typeStr);
+        if (!resType || entryStr.empty()) {
+            if (outError) {
+                std::stringstream errorStr;
+                errorStr << "invalid exported symbol at index="
+                         << util::deviceToHost32(entries[i].name.index)
+                         << " (" << str << ")";
+                *outError = errorStr.str();
+            }
+            return -1;
+        }
+
+        outRes->exportedSymbols.push_back(SourcedResourceName{
+                ResourceName{ packageStr.toString(), *resType, entryStr.toString() },
+                util::deviceToHost32(entries[i].line) });
+    }
+
+    const StringPiece16 str = util::getString(symbolPool,
+                                              util::deviceToHost32(fileExport->name.index));
+    StringPiece16 packageStr, typeStr, entryStr;
+    ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
+    const ResourceType* resType = parseResourceType(typeStr);
+    if (!resType || entryStr.empty()) {
+        if (outError) {
+            std::stringstream errorStr;
+            errorStr << "invalid resource name at index="
+                     << util::deviceToHost32(fileExport->name.index)
+                     << " (" << str << ")";
+            *outError = errorStr.str();
+        }
+        return -1;
+    }
+
+    outRes->name = ResourceName{ packageStr.toString(), *resType, entryStr.toString() };
+    outRes->source.path = util::utf16ToUtf8(
+            util::getString(symbolPool, util::deviceToHost32(fileExport->source.index)));
+    outRes->config.copyFromDtoH(fileExport->config);
+    return offset;
+}
+
+} // namespace aapt
+
+#endif /* AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H */
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
new file mode 100644
index 0000000..a76c83b
--- /dev/null
+++ b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+
+#include "flatten/FileExportWriter.h"
+#include "unflatten/FileExportHeaderReader.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(FileExportHeaderReaderTest, ReadHeaderWithNoSymbolExports) {
+    ResourceFile resFile = {
+            test::parseNameOrDie(u"@android:layout/main.xml"),
+            test::parseConfigOrDie("sw600dp-v4"),
+            Source{ "res/layout/main.xml" },
+    };
+
+    BigBuffer buffer(1024);
+    ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
+    *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
+    writer.finish();
+
+    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+
+    ResourceFile actualResFile;
+
+    ssize_t offset = unwrapFileExportHeader(data.get(), buffer.size(), &actualResFile, nullptr);
+    ASSERT_GT(offset, 0);
+
+    EXPECT_EQ(offset, getWrappedDataOffset(data.get(), buffer.size(), nullptr));
+
+    EXPECT_EQ(actualResFile.config, test::parseConfigOrDie("sw600dp-v4"));
+    EXPECT_EQ(actualResFile.name, test::parseNameOrDie(u"@android:layout/main.xml"));
+    EXPECT_EQ(actualResFile.source.path, "res/layout/main.xml");
+
+    EXPECT_EQ(*(uint32_t*)(data.get() + offset), 42u);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp
similarity index 76%
rename from tools/aapt2/ResChunkPullParser.cpp
rename to tools/aapt2/unflatten/ResChunkPullParser.cpp
index 78ea60e..6f8bb1b 100644
--- a/tools/aapt2/ResChunkPullParser.cpp
+++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#include "ResChunkPullParser.h"
+#include "unflatten/ResChunkPullParser.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <cstddef>
@@ -31,12 +32,11 @@
     if (mEvent == Event::StartDocument) {
         mCurrentChunk = mData;
     } else {
-        mCurrentChunk = reinterpret_cast<const ResChunk_header*>(
-                reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size);
+        mCurrentChunk = (const ResChunk_header*)
+                (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size));
     }
 
-    const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk)
-            - reinterpret_cast<const char*>(mData);
+    const std::ptrdiff_t diff = (const char*) mCurrentChunk - (const char*) mData;
     assert(diff >= 0 && "diff is negative");
     const size_t offset = static_cast<const size_t>(diff);
 
@@ -49,15 +49,16 @@
         return (mEvent = Event::BadDocument);
     }
 
-    if (mCurrentChunk->headerSize < sizeof(ResChunk_header)) {
+    if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) {
         mLastError = "chunk has too small header";
         mCurrentChunk = nullptr;
         return (mEvent = Event::BadDocument);
-    } else if (mCurrentChunk->size < mCurrentChunk->headerSize) {
+    } else if (util::deviceToHost32(mCurrentChunk->size) <
+            util::deviceToHost16(mCurrentChunk->headerSize)) {
         mLastError = "chunk's total size is smaller than header";
         mCurrentChunk = nullptr;
         return (mEvent = Event::BadDocument);
-    } else if (offset + mCurrentChunk->size > mLen) {
+    } else if (offset + util::deviceToHost32(mCurrentChunk->size) > mLen) {
         mLastError = "chunk's data extends past the end of the document";
         mCurrentChunk = nullptr;
         return (mEvent = Event::BadDocument);
diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h
similarity index 89%
rename from tools/aapt2/ResChunkPullParser.h
rename to tools/aapt2/unflatten/ResChunkPullParser.h
index 1426ed2..a51d5bf 100644
--- a/tools/aapt2/ResChunkPullParser.h
+++ b/tools/aapt2/unflatten/ResChunkPullParser.h
@@ -17,6 +17,8 @@
 #ifndef AAPT_RES_CHUNK_PULL_PARSER_H
 #define AAPT_RES_CHUNK_PULL_PARSER_H
 
+#include "util/Util.h"
+
 #include <androidfw/ResourceTypes.h>
 #include <string>
 
@@ -76,18 +78,18 @@
 
 template <typename T>
 inline static const T* convertTo(const android::ResChunk_header* chunk) {
-    if (chunk->headerSize < sizeof(T)) {
+    if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) {
         return nullptr;
     }
     return reinterpret_cast<const T*>(chunk);
 }
 
-inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) {
-    return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
+inline static const uint8_t* getChunkData(const android::ResChunk_header* chunk) {
+    return reinterpret_cast<const uint8_t*>(chunk) + util::deviceToHost16(chunk->headerSize);
 }
 
-inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) {
-    return chunk.size - chunk.headerSize;
+inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) {
+    return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize);
 }
 
 //
diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp
similarity index 97%
rename from tools/aapt2/BigBuffer.cpp
rename to tools/aapt2/util/BigBuffer.cpp
index 8f57172..c88e3c1 100644
--- a/tools/aapt2/BigBuffer.cpp
+++ b/tools/aapt2/util/BigBuffer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
 
 #include <algorithm>
 #include <memory>
diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/util/BigBuffer.h
similarity index 96%
rename from tools/aapt2/BigBuffer.h
rename to tools/aapt2/util/BigBuffer.h
index 8b6569c..cad2a2e 100644
--- a/tools/aapt2/BigBuffer.h
+++ b/tools/aapt2/util/BigBuffer.h
@@ -20,6 +20,7 @@
 #include <cassert>
 #include <cstring>
 #include <memory>
+#include <type_traits>
 #include <vector>
 
 namespace aapt {
@@ -124,6 +125,7 @@
 
 template <typename T>
 inline T* BigBuffer::nextBlock(size_t count) {
+    static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type");
     assert(count != 0);
     return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count));
 }
diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp
similarity index 98%
rename from tools/aapt2/BigBuffer_test.cpp
rename to tools/aapt2/util/BigBuffer_test.cpp
index 01ee8d7..2a24f12 100644
--- a/tools/aapt2/BigBuffer_test.cpp
+++ b/tools/aapt2/util/BigBuffer_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
+#include "util/BigBuffer.h"
 
 #include <gtest/gtest.h>
 
diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/util/Files.cpp
similarity index 67%
rename from tools/aapt2/Files.cpp
rename to tools/aapt2/util/Files.cpp
index b24ff6b..a81dc7b 100644
--- a/tools/aapt2/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#include "Files.h"
-#include "Util.h"
+#include "util/Files.h"
+#include "util/Util.h"
 
 #include <cerrno>
+#include <cstdio>
 #include <dirent.h>
 #include <string>
 #include <sys/stat.h>
@@ -28,6 +29,7 @@
 #endif
 
 namespace aapt {
+namespace file {
 
 FileType getFileType(const StringPiece& path) {
     struct stat sb;
@@ -61,15 +63,15 @@
     }
 }
 
-std::vector<std::string> listFiles(const StringPiece& root) {
+std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
     DIR* dir = opendir(root.data());
     if (dir == nullptr) {
-        Logger::error(Source{ root.toString() })
-            << "unable to open file: "
-            << strerror(errno)
-            << "."
-            << std::endl;
-        return {};
+        if (outError) {
+            std::stringstream errorStr;
+            errorStr << "unable to open file: " << strerror(errno);
+            *outError = errorStr.str();
+            return {};
+        }
     }
 
     std::vector<std::string> files;
@@ -105,17 +107,69 @@
     return mkdirImpl(path) == 0 || errno == EEXIST;
 }
 
-std::string getStem(const StringPiece& path) {
+StringPiece getStem(const StringPiece& path) {
     const char* start = path.begin();
     const char* end = path.end();
     for (const char* current = end - 1; current != start - 1; --current) {
         if (*current == sDirSep) {
-            return std::string(start, current - start);
+            return StringPiece(start, current - start);
         }
     }
     return {};
 }
 
+StringPiece getFilename(const StringPiece& path) {
+    const char* end = path.end();
+    const char* lastDirSep = path.begin();
+    for (const char* c = path.begin(); c != end; ++c) {
+        if (*c == sDirSep) {
+            lastDirSep = c + 1;
+        }
+    }
+    return StringPiece(lastDirSep, end - lastDirSep);
+}
+
+StringPiece getExtension(const StringPiece& path) {
+    StringPiece filename = getFilename(path);
+    const char* const end = filename.end();
+    const char* c = std::find(filename.begin(), end, '.');
+    if (c != end) {
+        return StringPiece(c, end - c);
+    }
+    return {};
+}
+
+std::string packageToPath(const StringPiece& package) {
+    std::string outPath;
+    for (StringPiece part : util::tokenize<char>(package, '.')) {
+        appendPath(&outPath, part);
+    }
+    return outPath;
+}
+
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) {
+    std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose };
+    if (!f) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+
+    int fd = fileno(f.get());
+
+    struct stat fileStats = {};
+    if (fstat(fd, &fileStats) != 0) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+
+    android::FileMap fileMap;
+    if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+    return std::move(fileMap);
+}
+
 bool FileFilter::setPattern(const StringPiece& pattern) {
     mPatternTokens = util::splitAndLowercase(pattern, ':');
     return true;
@@ -169,14 +223,10 @@
 
         if (ignore) {
             if (chatty) {
-                Logger::warn()
-                    << "skipping " <<
-                    (type == FileType::kDirectory ? "dir '" : "file '")
-                    << filename
-                    << "' due to ignore pattern '"
-                    << token
-                    << "'."
-                    << std::endl;
+                mDiag->warn(DiagMessage() << "skipping "
+                            << (type == FileType::kDirectory ? "dir '" : "file '")
+                            << filename << "' due to ignore pattern '"
+                            << token << "'");
             }
             return false;
         }
@@ -184,5 +234,5 @@
     return true;
 }
 
-
+} // namespace file
 } // namespace aapt
diff --git a/tools/aapt2/Files.h b/tools/aapt2/util/Files.h
similarity index 79%
rename from tools/aapt2/Files.h
rename to tools/aapt2/util/Files.h
index 844fd2b..c58ba5d 100644
--- a/tools/aapt2/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -17,15 +17,20 @@
 #ifndef AAPT_FILES_H
 #define AAPT_FILES_H
 
-#include "Logger.h"
+#include "Diagnostics.h"
+#include "Maybe.h"
 #include "Source.h"
-#include "StringPiece.h"
 
+#include "util/StringPiece.h"
+
+#include <utils/FileMap.h>
 #include <cassert>
+#include <memory>
 #include <string>
 #include <vector>
 
 namespace aapt {
+namespace file {
 
 #ifdef _WIN32
 constexpr const char sDirSep = '\\';
@@ -74,7 +79,28 @@
 /**
  * Returns all but the last part of the path.
  */
-std::string getStem(const StringPiece& path);
+StringPiece getStem(const StringPiece& path);
+
+/**
+ * Returns the last part of the path with extension.
+ */
+StringPiece getFilename(const StringPiece& path);
+
+/**
+ * Returns the extension of the path. This is the entire string after
+ * the first '.' of the last part of the path.
+ */
+StringPiece getExtension(const StringPiece& path);
+
+/**
+ * Converts a package name (com.android.app) to a path: com/android/app
+ */
+std::string packageToPath(const StringPiece& package);
+
+/**
+ * Creates a FileMap for the file at path.
+ */
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError);
 
 /*
  * Filter that determines which resource files/directories are
@@ -84,6 +110,9 @@
  */
 class FileFilter {
 public:
+    FileFilter(IDiagnostics* diag) : mDiag(diag) {
+    }
+
     /*
      * Patterns syntax:
      * - Delimiter is :
@@ -106,6 +135,7 @@
     bool operator()(const std::string& filename, FileType type) const;
 
 private:
+    IDiagnostics* mDiag;
     std::vector<std::string> mPatternTokens;
 };
 
@@ -123,6 +153,7 @@
     appendPath(base, parts...);
 }
 
+} // namespace file
 } // namespace aapt
 
 #endif // AAPT_FILES_H
diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/util/Maybe.h
similarity index 99%
rename from tools/aapt2/Maybe.h
rename to tools/aapt2/util/Maybe.h
index ff6625f..1f7d5ce 100644
--- a/tools/aapt2/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -72,7 +72,7 @@
      * True if this holds a value, false if
      * it holds Nothing.
      */
-    operator bool() const;
+    explicit operator bool() const;
 
     /**
      * Gets the value if one exists, or else
diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
similarity index 93%
rename from tools/aapt2/Maybe_test.cpp
rename to tools/aapt2/util/Maybe_test.cpp
index 71bbb94..d2c33ca 100644
--- a/tools/aapt2/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
+#include "test/Common.h"
+#include "util/Maybe.h"
+
 #include <gtest/gtest.h>
 #include <string>
 
-#include "Maybe.h"
-
 namespace aapt {
 
 struct Dummy {
@@ -85,22 +86,22 @@
 
 TEST(MaybeTest, MakeNothing) {
     Maybe<int> val = make_nothing<int>();
-    EXPECT_FALSE(val);
+    AAPT_EXPECT_FALSE(val);
 
     Maybe<std::string> val2 = make_nothing<std::string>();
-    EXPECT_FALSE(val2);
+    AAPT_EXPECT_FALSE(val2);
 
     val2 = make_nothing<std::string>();
-    EXPECT_FALSE(val2);
+    AAPT_EXPECT_FALSE(val2);
 }
 
 TEST(MaybeTest, MakeSomething) {
     Maybe<int> val = make_value(23);
-    ASSERT_TRUE(val);
+    AAPT_ASSERT_TRUE(val);
     EXPECT_EQ(23, val.value());
 
     Maybe<std::string> val2 = make_value(std::string("hey"));
-    ASSERT_TRUE(val2);
+    AAPT_ASSERT_TRUE(val2);
     EXPECT_EQ(std::string("hey"), val2.value());
 }
 
diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/util/StringPiece.h
similarity index 100%
rename from tools/aapt2/StringPiece.h
rename to tools/aapt2/util/StringPiece.h
diff --git a/tools/aapt2/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp
similarity index 98%
rename from tools/aapt2/StringPiece_test.cpp
rename to tools/aapt2/util/StringPiece_test.cpp
index 43f7a37..d49b67f 100644
--- a/tools/aapt2/StringPiece_test.cpp
+++ b/tools/aapt2/util/StringPiece_test.cpp
@@ -19,7 +19,7 @@
 #include <string>
 #include <vector>
 
-#include "StringPiece.h"
+#include "util/StringPiece.h"
 
 namespace aapt {
 
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/util/Util.cpp
similarity index 87%
rename from tools/aapt2/Util.cpp
rename to tools/aapt2/util/Util.cpp
index ca352e0..f219b65 100644
--- a/tools/aapt2/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "StringPiece.h"
-#include "Util.h"
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
 
 #include <algorithm>
 #include <ostream>
@@ -122,6 +122,29 @@
     return pieces >= 2;
 }
 
+bool isJavaPackageName(const StringPiece16& str) {
+    if (str.empty()) {
+        return false;
+    }
+
+    size_t pieces = 0;
+    for (const StringPiece16& piece : tokenize(str, u'.')) {
+        pieces++;
+        if (piece.empty()) {
+            return false;
+        }
+
+        if (piece.data()[0] == u'_' || piece.data()[piece.size() - 1] == u'_') {
+            return false;
+        }
+
+        if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) {
+            return false;
+        }
+    }
+    return pieces >= 1;
+}
+
 Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
                                                  const StringPiece16& className) {
     if (className.empty()) {
@@ -338,5 +361,29 @@
     return {};
 }
 
+bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
+                             StringPiece16* outEntry, StringPiece16* outSuffix) {
+    if (!stringStartsWith<char16_t>(path, u"res/")) {
+        return false;
+    }
+
+    StringPiece16::const_iterator lastOccurence = path.end();
+    for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) {
+        if (*iter == u'/') {
+            lastOccurence = iter;
+        }
+    }
+
+    if (lastOccurence == path.end()) {
+        return false;
+    }
+
+    auto iter = std::find(lastOccurence, path.end(), u'.');
+    *outSuffix = StringPiece16(iter, path.end() - iter);
+    *outEntry = StringPiece16(lastOccurence + 1, iter - lastOccurence - 1);
+    *outPrefix = StringPiece16(path.begin(), lastOccurence - path.begin() + 1);
+    return true;
+}
+
 } // namespace util
 } // namespace aapt
diff --git a/tools/aapt2/Util.h b/tools/aapt2/util/Util.h
similarity index 90%
rename from tools/aapt2/Util.h
rename to tools/aapt2/util/Util.h
index 7ec6b03..402147d 100644
--- a/tools/aapt2/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -17,9 +17,9 @@
 #ifndef AAPT_UTIL_H
 #define AAPT_UTIL_H
 
-#include "BigBuffer.h"
-#include "Maybe.h"
-#include "StringPiece.h"
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <functional>
@@ -83,6 +83,11 @@
 bool isJavaClassName(const StringPiece16& str);
 
 /**
+ * Tests that the string is a valid Java package name.
+ */
+bool isJavaPackageName(const StringPiece16& str);
+
+/**
  * Converts the class name to a fully qualified class name from the given `package`. Ex:
  *
  * asdf         --> package.asdf
@@ -296,6 +301,22 @@
         mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
 }
 
+inline uint16_t hostToDevice16(uint16_t value) {
+    return htods(value);
+}
+
+inline uint32_t hostToDevice32(uint32_t value) {
+    return htodl(value);
+}
+
+inline uint16_t deviceToHost16(uint16_t value) {
+    return dtohs(value);
+}
+
+inline uint32_t deviceToHost32(uint32_t value) {
+    return dtohl(value);
+}
+
 /**
  * Returns a package name if the namespace URI is of the form:
  * http://schemas.android.com/apk/res/<package>
@@ -305,6 +326,18 @@
  */
 Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
 
+/**
+ * Given a path like: res/xml-sw600dp/foo.xml
+ *
+ * Extracts "res/xml-sw600dp/" into outPrefix.
+ * Extracts "foo" into outEntry.
+ * Extracts ".xml" into outSuffix.
+ *
+ * Returns true if successful.
+ */
+bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
+                             StringPiece16* outEntry, StringPiece16* outSuffix);
+
 } // namespace util
 
 /**
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
new file mode 100644
index 0000000..cdba960
--- /dev/null
+++ b/tools/aapt2/util/Util_test.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Common.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+TEST(UtilTest, TrimOnlyWhitespace) {
+    const std::u16string full = u"\n        ";
+
+    StringPiece16 trimmed = util::trimWhitespace(full);
+    EXPECT_TRUE(trimmed.empty());
+    EXPECT_EQ(0u, trimmed.size());
+}
+
+TEST(UtilTest, StringEndsWith) {
+    EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
+}
+
+TEST(UtilTest, StringStartsWith) {
+    EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
+}
+
+TEST(UtilTest, StringBuilderSplitEscapeSequence) {
+    EXPECT_EQ(StringPiece16(u"this is a new\nline."),
+              util::StringBuilder().append(u"this is a new\\")
+                                   .append(u"nline.")
+                                   .str());
+}
+
+TEST(UtilTest, StringBuilderWhitespaceRemoval) {
+    EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
+              util::StringBuilder().append(u"    hey guys ")
+                                   .append(u" this is so cool ")
+                                   .str());
+
+    EXPECT_EQ(StringPiece16(u" wow,  so many \t spaces. what?"),
+              util::StringBuilder().append(u" \" wow,  so many \t ")
+                                   .append(u"spaces. \"what? ")
+                                   .str());
+
+    EXPECT_EQ(StringPiece16(u"where is the pie?"),
+              util::StringBuilder().append(u"  where \t ")
+                                   .append(u" \nis the "" pie?")
+                                   .str());
+}
+
+TEST(UtilTest, StringBuilderEscaping) {
+    EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
+              util::StringBuilder().append(u"    hey guys\\n ")
+                                   .append(u" this \\t is so\\\\ cool ")
+                                   .str());
+
+    EXPECT_EQ(StringPiece16(u"@?#\\\'"),
+              util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
+                                   .str());
+}
+
+TEST(UtilTest, StringBuilderMisplacedQuote) {
+    util::StringBuilder builder{};
+    EXPECT_FALSE(builder.append(u"they're coming!"));
+}
+
+TEST(UtilTest, StringBuilderUnicodeCodes) {
+    EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
+              util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
+                                   .str());
+
+    EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
+}
+
+TEST(UtilTest, TokenizeInput) {
+    auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
+    auto iter = tokenizer.begin();
+    ASSERT_EQ(*iter, StringPiece16(u"this"));
+    ++iter;
+    ASSERT_EQ(*iter, StringPiece16(u" is"));
+    ++iter;
+    ASSERT_EQ(*iter, StringPiece16(u"the"));
+    ++iter;
+    ASSERT_EQ(*iter, StringPiece16(u"end"));
+    ++iter;
+    ASSERT_EQ(tokenizer.end(), iter);
+}
+
+TEST(UtilTest, TokenizeAtEnd) {
+    auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.');
+    auto iter = tokenizer.begin();
+    ASSERT_EQ(*iter, StringPiece16(u"one"));
+    ++iter;
+    ASSERT_NE(iter, tokenizer.end());
+    ASSERT_EQ(*iter, StringPiece16());
+}
+
+TEST(UtilTest, IsJavaClassName) {
+    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
+    EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
+    EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
+    EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
+    EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
+    EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
+    EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
+    EXPECT_FALSE(util::isJavaClassName(u"android"));
+}
+
+TEST(UtilTest, IsJavaPackageName) {
+    EXPECT_TRUE(util::isJavaPackageName(u"android"));
+    EXPECT_TRUE(util::isJavaPackageName(u"android.test"));
+    EXPECT_TRUE(util::isJavaPackageName(u"android.test_thing"));
+    EXPECT_FALSE(util::isJavaPackageName(u"_android"));
+    EXPECT_FALSE(util::isJavaPackageName(u"android_"));
+    EXPECT_FALSE(util::isJavaPackageName(u"android."));
+    EXPECT_FALSE(util::isJavaPackageName(u".android"));
+    EXPECT_FALSE(util::isJavaPackageName(u"android._test"));
+    EXPECT_FALSE(util::isJavaPackageName(u".."));
+}
+
+TEST(UtilTest, FullyQualifiedClassName) {
+    Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"android.asdf");
+
+    res = util::getFullyQualifiedClassName(u"android", u".asdf");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"android.asdf");
+
+    res = util::getFullyQualifiedClassName(u"android", u".a.b");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"android.a.b");
+
+    res = util::getFullyQualifiedClassName(u"android", u"a.b");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"a.b");
+
+    res = util::getFullyQualifiedClassName(u"", u"a.b");
+    AAPT_ASSERT_TRUE(res);
+    EXPECT_EQ(res.value(), u"a.b");
+
+    res = util::getFullyQualifiedClassName(u"", u"");
+    AAPT_ASSERT_FALSE(res);
+
+    res = util::getFullyQualifiedClassName(u"android", u"./Apple");
+    AAPT_ASSERT_FALSE(res);
+}
+
+TEST(UtilTest, ExtractResourcePathComponents) {
+    StringPiece16 prefix, entry, suffix;
+    ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.xml", &prefix, &entry,
+                                              &suffix));
+    EXPECT_EQ(prefix, u"res/xml-sw600dp/");
+    EXPECT_EQ(entry, u"entry");
+    EXPECT_EQ(suffix, u".xml");
+
+    ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.9.png", &prefix, &entry,
+                                              &suffix));
+
+    EXPECT_EQ(prefix, u"res/xml-sw600dp/");
+    EXPECT_EQ(entry, u"entry");
+    EXPECT_EQ(suffix, u".9.png");
+
+    EXPECT_FALSE(util::extractResFilePathParts(u"AndroidManifest.xml", &prefix, &entry, &suffix));
+    EXPECT_FALSE(util::extractResFilePathParts(u"res/.xml", &prefix, &entry, &suffix));
+
+    ASSERT_TRUE(util::extractResFilePathParts(u"res//.", &prefix, &entry, &suffix));
+    EXPECT_EQ(prefix, u"res//");
+    EXPECT_EQ(entry, u"");
+    EXPECT_EQ(suffix, u".");
+}
+
+} // namespace aapt
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index a410c53..e9b7819 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -152,6 +152,11 @@
     }
 
     @Override
+    public boolean isLightDeviceIdleMode() throws RemoteException {
+        return false;
+    }
+
+    @Override
     public boolean isScreenBrightnessBoosted() throws RemoteException {
         return false;
     }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index ff8d6d4d..1970542 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1086,17 +1086,8 @@
     public WifiActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
         if (mService == null) return null;
         try {
-            WifiActivityEnergyInfo record;
-            if (!isEnhancedPowerReportingSupported()) {
-                return null;
-            }
             synchronized(this) {
-                record = mService.reportActivityInfo();
-                if (record != null && record.isValid()) {
-                    return record;
-                } else {
-                    return null;
-                }
+                return mService.reportActivityInfo();
             }
         } catch (RemoteException e) {
             Log.e(TAG, "getControllerActivityEnergyInfo: " + e);
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index a65f250..c26ca6e 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -709,6 +709,7 @@
         validateChannel();
         HotlistSettings settings = new HotlistSettings();
         settings.bssidInfos = bssidInfos;
+        settings.apLostThreshold = apLostThreshold;
         sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
     }